• 线程同步的方式


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

  • 相关阅读:
    redis要注意的一些知识
    redis数据类型及常用命令使用
    基于zookeeper的分布式锁
    基于redis的分布式锁
    基于数据库的分布式锁
    数据库的4中隔离级别
    远程调用历史及代码编写demo
    数据库的ACID跟事务隔离级别
    JAVA8 十大新特性详解
    java8 :: 用法 (JDK8 双冒号用法)
  • 原文地址:https://www.cnblogs.com/Joezzz/p/10067686.html
Copyright © 2020-2023  润新知