线程同步的方式

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组数据再进行排序