C++中关于互斥量的全面认知
作者:Gy648
互斥量(保护对共享变量的访问)
1.概念
互斥(mutex)是防止同时访问共享资源的程序对象。
为避免线程更新共享变量时所出现问题,必须使用互斥量( mutex 是 mutual exclusion 的 缩写)来确保同时仅有一个线程可以访问某项共享资源。 即就是 使用互斥量来实现原子访问操作
2.状态
已锁定( locked)和未锁定( unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法
3.特点
一旦线程锁定互斥量,随即成为该互斥量的所有者。只有所有者才能给互斥量解锁。因为所有权的关系,有时会使用术语获取( acquire)和释放( release)来替代加锁和解锁。
互斥量的分配
互斥量既可以像静态变量那样分配,也可以在运行时动态创建
1.静态分配
互斥量是属于 pthread_mutex_t 类型的变量
在使用之前必须对其初始化。
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2.动态分配
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数 mutex 指定函数执行初始化操作的目标互斥量。
参数 attr 是指向 pthread_mutexattr_t 类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。若将 attr 参数置为 NULL,则该互斥量的各种属性会取默认值。
注:
- 初始化一个业已初始化的互斥量将导致未定义的行为
- 动态分配于堆中的互斥量。例如,动态创建针对某一结构的链表,表中每个结构都包含一个 pthread_mutex_t 类型的字段来存放互斥量,借以保护对该结构的访问。
- 互斥量是在栈中分配的自动变量。
- 初始化经由静态分配,且不使用默认属性的互斥量。
加锁和解锁互斥量
初始化之后,互斥量处于未锁定状态。函数 pthread_mutex_lock()可以锁定某一互斥量,而函数 pthread_mutex_unlock()则可以将一个互斥量解锁。
函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
1.创建互斥锁
pthread_mutex_t mtx;
互斥锁的类型是 pthread_mutex_t ,所以定义一个变量就是创建了一个互斥锁:
2.初始化互斥锁
//第二个参数为 NULL,互斥锁的属性会设置为默认属性 pthread_mutex_init(&mtx, NULL);
3.获取互斥锁
在进行互斥操作的时候, 应该先"拿到锁"再执行需要互斥的操作,否则可能会导致多个线程都需要访问的数据结果不一致。
4.阻塞调用
pthread_mutex_lock(&mtx);
5.非阻塞调用
如果锁被占用就不用,如果没被占用那就用, 可以使用 pthread_mutex_trylock() 函数。 用法和pthread_mutex_lock() 用法类似,不过当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。
int err = pthread_mutex_trylock(&mtx); if(0 != err) { if(EBUSY == err) { //The mutex could not be acquired because it was already locked. } }
6.超时调用
如果不想不断的调用 pthread_mutex_trylock() 来测试互斥锁是否可用, 而是想阻塞调用,但是增加一个超时时间, 用pthread_mutex_timedlock() 解决, 其调用方式如下:
struct timespec abs_timeout; abs_timeout.tv_sec = time(NULL) + 1; abs_timeout.tv_nsec = 0; int err = pthread_mutex_timedlock(&mtx, &abs_timeout); if(0 != err) { if(ETIMEDOUT == err) { //The mutex could not be locked before the specified timeout expired. } }
阻塞等待,但是只等待一秒钟,后如果还没拿到锁的话, 那就返回,并返回一个错误代码 ETIMEDOUT,意思是超时了。
其中 timespec 定义在头文件 time.h 中,其定义如下
struct timespec { __time_t tv_sec; /* Seconds. */ long int tv_nsec; /* Nanoseconds. */ };
这个函数里面的时间,是绝对时间,所以这里用 time() 函数返回的时间增加了 1 秒
7.释放互斥锁
用完互斥锁,一定要记得释放,下一个想要获得这个锁的线程, 只能去等。
释放互斥锁比较简单,使用 pthread_mutex_unlock() 即可:
pthread_mutex_unlock(&mtx);
8.销毁线程锁
pthread_mutex_destroy(&mtx)
一个被销毁的线程锁可以被 pthread_mutex_init() 再次初始化。对被销毁的线程锁进行其它操作,其结果是未定义的。
对一个处于已初始化但未锁定状态的线程锁进行销毁是安全的。尽量避免对一个处于锁定状态的线程锁进行销毁操作。
互斥量的死锁
当超过一个线程加锁同一组互斥量时,就有可能发生死锁。
例,每个线程都成功地锁住一个互斥量,接着试图对已为另一线程锁定的互斥量加锁。
两个线程将无限期等待
有两种解决方法
1.当多个线程对一组互斥量操作时,总是应该以相同顺序对该组互斥量进行锁定,如果两个线程总是先锁定 mutex1 再锁定 mutex2,死锁就不会出现
2.使用频率较低,就是“尝试一下,然后恢复”,在这种方案中,线程先使用函数pthread_mutex_lock()
锁定第 1 个互斥量,然后使用函数pthread_mutex_trylock()
来锁定其余互斥量。如果任一pthread_mutex_trylock()
调用失败(返回 EBUSY),那么该线程将释放所有 互斥量,也许经过一段时间间隔,从头再试
注:
- 对共享资源操作前一定要获得锁。
- 完成操作以后一定要释放锁。
- 尽量短时间地占用锁。
- 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
- 线程错误返回时应该释放它所获得的锁。
例子
保护fp指向文件中数的累加正常进行
static pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER; void *thr_prime(void *p) { FILE *fp; char linebuf[linesize]; fp =fopen(fname,"r+"); //多个线程之间相撞拿到 同一个fp 开始覆盖写操作 if(fp == NULL) { perror("fopen"); exit(-1); } //加锁 pthread_mutex_lock(&mut); fgets(linebuf,linesize,fp); fseek(fp,0,SEEK_SET); fprintf(fp,"%d\n",atoi(linebuf)+1); //解锁 pthread_mutex_unlock(&mut); fclose(fp); pthread_exit(NULL); } int main() { int err,i; pthread_t tid[thrnum]; //main 线程 进行创建线程 for(i=0 ; i<=thrnum ;i++) { err =pthread_create(tid + i ,NULL,thr_prime,NULL); if(err) { fprintf(stderr,"pthread_creat():%s\n",strerror(err)); exit(1); } } //为线程收尸 for(i =0 ; i<=thrnum ;i++) { pthread_join(tid[i],NULL); } pthread_mutex_destroy(&mut); exit(0); }
到此这篇关于C++中关于互斥量的全面认知的文章就介绍到这了,更多相关C++互斥量内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!