对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但,锁本身不具备强制性。
使用mutex(互斥量、互斥锁)一般步骤:
pthread_mutex_init 函数
初始化一个互斥锁(互斥量) —> 初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参 1:传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改
参 2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4 同步属性
- 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
- 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)
pthread_mutex_destroy 函数
销毁一个互斥锁
pthread_mutex_lock 函数
加锁。可理解为将mutex–(或 -1),操作后mutex 的值为 0。
pthread_mutex_unlock 函数
解锁。可理解为将mutex ++(或 +1),操作后mutex 的值为 1。
pthread_mutex_trylock 函数
尝试加锁
创建流程:
pthread_mutex_t 类型。
1. pthread_mutex_t lock; 创建锁
2 pthread_mutex_init; 初始化
3. pthread_mutex_lock;加锁
4. 访问共享数据(stdout)
5. pthrad_mutext_unlock();解锁
6. pthead_mutex_destroy;销毁锁
加锁与解锁
lock 尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock 主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。
例如:T1 T2 T3 T4 使用一把 mutex 锁。T1 加锁成功,其他线程均阻塞,直至T1 解锁。T1 解锁后,T2 T3 T4 均被唤醒,并自动再次尝试加锁。
注意事项:
尽量保证锁的粒度, 越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)
互斥锁,本质是结构体。 我们可以看成整数。 初值为 1。(pthread_mutex_init() 函数调用成功。)
加锁: --操作, 阻塞线程。
解锁: ++操作, 换醒阻塞在锁上的线程。
try锁:尝试加锁,成功–。失败,返回。同时设置错误号 EBUSY
互斥锁demo
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<stdio.h>
pthread_mutex_t mutex; //定义一把互斥锁
void *tfn(void *arg){
srand(time(NULL));
while(1){
pthread_mutex_lock(&mutex);
printf("hello ");
sleep(rand()%3);
printf("world\n");
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
return NULL;
}
int main(void){
pthread_t tid;
srand(time(NULL));
int ret=pthread_mutex_init(&mutex,NULL); //初始化互斥锁
if(ret!=0){
fprintf(stderr,"mute init error:%s\n",sterror(ret));
exit(1);
}
pthread_create(&tid,NULL,tfn,NULL);
while(1){
pthread_mutex_lock(&mutex);
printf("HELL0 ");
sleep(rand()%3);
printf("WORLD\n");
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
pthread_join(tid,NULL);
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}