1.互斥量
1)只有两个状态:加锁状态,不加锁状态
2)当互斥量处于加锁状态时,任何试图再次加锁的行为都将被休眠阻塞
3)互斥量必须初始化,对于静态分配的互斥量,设置成一个特定的常量来初始化;对于动态分配的互斥量,调用其相应的_init函数初始化
4)如果是动态分配的互斥量,则在使用完之后要调用其相应的_destroy函数释放内存,静态分配的则不用
2.读写锁
1)有三个状态:读加锁状态,写加锁状态,不加锁状态
2)当读写锁处于写加锁状态时,任何试图再次加锁(读和写都是)的行为都将被休眠阻塞
3)当读写锁处于读加锁状态时,读模式下的加锁行为可以进行,写模式下的加锁的行为将被休眠阻塞
4)当读写锁处于读加锁状态时,如果此时有线程试图进行写模式加锁,通常这个时候读写锁会阻塞随后的读模式加锁,这样就可以避免读模式锁长期占用,写模式请求得不到满足的情况
5)读写锁也必须初始化(只能调用其相应的_init函数),且在使用完之后一定要调用其相应的_destroy函数释放内存
6)适用场景:读写锁非常适用于对数据结构的读次数远大于写次数的情况
3.条件变量
1)条件变量利用线程间共享的全局变量进行同步
2)适用场景:如果没有条件变量,程序员需要让线程不断地轮询,以检查是否满足条件,此时线程处于一个不间断的忙碌状态,这相当耗资源;条件变量使线程不需要轮询,而是让其被休眠阻塞,等待条件发生
3)通常条件变量和互斥量一起使用,条件(不是条件变量)由互斥量保护
4)条件变量必须初始化,对于静态分配的条件变量,设置成一个特定的常量来初始化;对于动态分配的条件变量,调用其相应的_init函数初始化
5)典型使用过程:
int condition = 0; pthread_cond_t qready = PTHREAD_COND_INITIALIZER; pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; //等待条件,线程1 void process_msg(void) { struct msge *mp; for (;;) { pthread_mutex_lock(&qlock); while (condition == 0) //while循环起到再一次检查的效果 { /* 1.以原子方式解锁互斥量 2.等待条件被满足 3.pthread_cond_signal发送条件成立信号时,以原子方式对互斥量加锁 */ pthread_cond_wait(&qready, &qlock); } condition = 0; pthread_mutex_unlock(&qlock); /* now process the message mp */ } } //产生条件,线程2 void enqueue_msg() { pthread_mutex_lock(&qlock); condition = 1; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); //如果多个线程处于等待状态,那么应使用pthreads_cond_broadcast()函数 }
6)pthread_cond_wait的存放位置
pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间:用互斥量保护条件,其他线程再获得互斥量之前不能读取或修改条件
7)pthread_cond_signal的存放位置
位置一:
pthread_mutex_lock /*……*/ pthread_cond_signal pthread_mutex_unlock //当等待线程被唤醒时,它重新锁住互斥量,但是如果此时互斥量还未解锁,则等待线程被阻塞,互斥量成功解锁后,等待线程再次被唤醒并重新锁住互斥量,从而_wait执行完毕。这样被唤醒两次,损耗系统
性能;但是在linux下,不会有这个问题,因为在linux线程中,使用两个队列,分别是cond_wait队列和mutex_lock队列,cond_signal只是让等待线程从cond_wait队列移到mutex_lock队列,即使
出现特殊情况也不会被唤醒两次,不会有性能的损耗
位置二:
pthread_mutex_lock /*……*/ pthread_mutex_unlock pthread_cond_signal //把_signal放在_unlock后面,不会有潜在的损耗,但是如果_unlock和_signal之间,有其他线程正在mutex上阻塞的话,那么这个线程就会抢占cond_wait的线程
8)条件变量经常和while配合使用:_wait函数的返回可能是意外返回,此时并不是其他线程释放了条件成立信号,所以此时条件其实没有被满足,用while循坏可以起到检查的效果
4.自旋锁
1)自旋锁与互斥量基本一样,不同的是如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,是一种忙等阻塞;而互斥量采用休眠阻塞,会让出cpu
2)适用场景:锁被持有的时间短(忙等阻塞时cpu不能做其他的事情,持有时间太长造成CPU资源浪费),线程不希望在重新调度上花太多成本;自旋锁在非抢占式内核中是非常有用:在实现线程互斥的同时,可以阻塞中断,这样系统就不会陷入死锁
5.屏障
1)屏障是用户协调多个线程并行工作的同步机制
2)屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行
3)pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出;屏障的使用范围更广,允许任意数量的线程等待,直到所有的线程完成处理工作,并且线程不用退出,所有的线程到达屏障以后可以接着工作
4)应用举例:
现有800万个数据要进行排序。现在创建出9个线程,一个主线程和8个工作线程。每个工作线程分别对100万个数据进行堆排序,主线程中设置屏障,等待8个线程完成数据的排序后,对8个线程排好序的8组数据再进行排序