线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
例如 :
出现这种情况是因为操作系统在内核中对线程的调用的来回切换。代码可以并发的切换到其他的线程。
互斥锁以排他方式防止共享数据被并发访问。
互斥锁是一个二元变量,只有两种状态 开锁 和 上锁。
将某个共享资源与某个特定互斥锁在逻辑上绑定。
在访问共享资源时,首先申请互斥锁,如果该互斥处于开锁状态,则申请到该锁对象,并立即上锁,防止其他线程访问该资源。如果互斥锁处于锁定状态,默认阻塞当前进程。
只有锁定该互斥锁的进程才能释放该互斥锁。
动态分配
在使用互斥锁前,需要定义该互斥锁
pthread_mutex_t lock;
pthread_mutex_init() 初始化函数
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
第一个参数为要初始化的互斥锁。
第二个参数指向属性对象的指针,该属性对象定义要初始化的互斥锁的属性。
静态分配
利用宏 PTHREAD_MUTEX_INITIALIZER 初始化静态分配的互斥锁
pthread_mutex_destroy() 函数
int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意:
静态分配的互斥量不需要销毁。
不要销毁一个已经加锁的互斥量。
已经销毁的互斥量要保证后面不会有线程再尝试加锁。
pthread_mutex_lock() 函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
函数以阻塞方式申请互斥锁
互斥锁处于未加锁状态,函数会将互斥量锁定,同时返回成功。
如果互斥锁处于加锁状态,或者未能竞争到锁资源,则函数陷入阻塞,等待互斥锁解锁。
非阻塞方式申请互斥锁的函数为
int pthread_mutex_trylock(pthread_mutex_t *mutex);
pthread_mutex_unlock()
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,否则返回指明错误的错误编号。
释放锁资源只能由占有该互斥锁的线程完成。
前面介绍的互斥锁可以解决资源的互斥访问,但并不完美。
例如:我们有一个全局变量 i,j 线程 A 和 B都要访问,线程A需要互斥的执行i++,j–,线程 B 需要互斥的在i == j 的时候执行 do()操作。
此时如果只是用互斥锁 ,可能导致 do() 永远执行不到。
分析:
1. 线程 A 抢占到互斥锁,执行操作,完成释放互斥锁。
2. 线程 A 和 B 都有可能抢占到锁,如果 B 抢占到,条件不满足,退出。
如果 A 抢占到 ,则执行操作。
整体来看,在整个程序中线程 B 仅仅只有一种情况需要执行 do() 操作。但是 B 有可能会争取到锁,不停的申请和释放互斥锁将造成资源的浪费。
所以此时我们就需要用到条件变量。
我们对上面的代码进行改进:
如果线程 B 抢占到互斥锁,在 i != j 的 情况下,释放互斥锁,使线程等待该条件变量。
如果线程 A 抢占到互斥锁,在 i != j 时继续执行,而在条件变量满足时释放互斥锁。并通知线程 B,从而使 do() 得以执行,提高了访问效率。
条件变量不能单独使用,需要配合互斥锁一起实现对资源的互斥访问。
动态分配:
pthread_cond_init() 函数
在使用条件变量前,需要定义条件变量
pthread_cond_t condtion;
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
第一个参数为只想要初始化或损坏的条件变量的指针
第二个参数为指向属性对象的指针
静态分配:
同互斥量相同:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy()函数
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_signal() 函数
int pthread_cond_signal(pthread_cond_t *cond);
用来通知等待条件变量的第一个线程
如果 cond 上没有阻塞任何线程,则函数不起作用。
int pthread_cond_broadcast(pthread_cond_t *cond);
用来通知等待条件变量的所有线程
pthread_cond_wait() 函数
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
第一个参数指向要等待的条件变量
第二个参数指向与条件变量 cond 相关联的互斥锁的指针
如果某线程因等待条件变量进入等待状态时,将隐含释放其申请的互斥锁。
在返回时,隐含申请到该互斥锁的操作。
此时我们介绍的信号量是 POSIX 信号量,前面进程间通信所用到的信号量是 SYSTEM V 信号量,不同的是 SYSTEM V中的信号量只能用于进程间同步。
sem_init() 函数
int sem_init(sem_t *sem, int pshared, unsigned int value);
第一个参数和前面的互斥量,条件变量相同
第二个参数 pshared == 0 表示在线程间共享
pshared != 0 表示在进程间共享
第三个参数 信号量初始值
sem_destroy()函数
int sem_destroy(sem_t *sem);
sem_wait() 函数
int sem_wait(sem_t *sem);
等待信号量,会将信号量 -1
sem_post()
int sem_post(sem_t *sem);
发布信号量,会将信号量 +1
当我们在对数据的读写操作时,很多情况下是大量的读操作,而少量的写操作。
显然,此时使用互斥锁也会极大的影响性能。
此时读写锁机制就可以用来处理这种读多写少的情况
定义全局变量
pthread_rwlock_t rwlock;
pthread_rwlock_init()函数
int pthread_rwlock_init(pthread_rwlock_t rwlock,
const pthread_rwlockattr_t* restrict attr);
第一个参数指向要初始化的读写锁指针
第二个参数指向属性对象的指针
静态初始化
PHTREAD_RWLOCK_INITIALIZER 宏 进行静态初始化
pthread_rwlock_destroy() 函数销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
函数成功返回 0 ,否则返回错误编号。
pthread_rwlock_rdlock() 函数
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
函数以阻塞方式来申请读锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
函数以非阻塞方式来申请读锁
ptherad_rwlock_wrlock()函数
int pthread_rwlock_wrlock(pthread_lock_t *rwlock);
函数以阻塞方式来申请写锁
int phtread_rwlock_trywrlock(pthread_lock_t *rwlock);
函数以非阻塞方式来申请写锁
pthread_rwlock_unlock()
ptherad_rwlock_unlock(pthread_rwlock_t *rwlock);
如果用来释放读锁,但当前还有其他读锁定时,则保持读锁定状态。
如果释放的是最后一个读锁或写锁,则读写锁将处于解锁状态。
成功返回 0 ,否则返回错误编号并指明错误