zoukankan      html  css  js  c++  java
  • 线程同步

    线程同步

             同步即协同步调,按预定的先后次序运行。

             线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

             “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。

             因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。

    数据混乱原因:

             1. 资源共享(独享资源则不会)       

        2. 调度随机(意味着数据访问会出现竞争)  

        3. 线程间缺乏必要的同步机制。

    一、互斥量mutex

    Linux中提供一把互斥锁mutex(也称之为互斥量)。

    每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

    资源还是共享的,线程间也还是竞争的,                                                                

    但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

    注意:同一时刻,只能有一个线程持有该锁。

    主要应用函数:

     pthread_mutex_init函数
    
     pthread_mutex_destroy函数
    
     pthread_mutex_lock函数
    
     pthread_mutex_trylock函数
    
     pthread_mutex_unlock函数

    以上5个函数的返回值都是:成功返回0, 失败返回错误号。   

    pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。

    pthread_mutex_t mutex; 变量mutex只有两种取值1、0。

    pthread_mutex_init函数

    初始化一个互斥锁(互斥量) ---> 初值可看作1

    
    
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    
             参1:传出参数,调用时应传 &mutex      
    
             参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 
    
    
    1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g.  pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
    2. 动态初始化:局部变量应采用动态初始化。e.g.  pthread_mutex_init(&mutex, NULL)

    pthread_mutex_destroy函数

    销毁一个互斥锁

             int pthread_mutex_destroy(pthread_mutex_t *mutex);

    pthread_mutex_lock函数

    加锁。可理解为将mutex--(或-1)

             int pthread_mutex_lock(pthread_mutex_t *mutex);

    pthread_mutex_unlock函数

    解锁。可理解为将mutex ++(或+1)

             int pthread_mutex_unlock(pthread_mutex_t *mutex);

    pthread_mutex_trylock函数

    尝试加锁

             int pthread_mutex_trylock(pthread_mutex_t *mutex);

    lock与unlock:

             lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。

             unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。

    lock与trylock:

             lock加锁失败会阻塞,等待锁释放。

             trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。

    二、死锁

    产生原因

           1. 线程试图对同一个互斥量A加锁两次。

           2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

      3、振荡

    避免方法

      1、保证资源的获取顺序,要求每个线程获取资源的顺序一致

      2、当得不到所有所需资源时,放弃已经获得的资源,等待

    三、读写锁

       与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享,写锁优先级高

    主要应用函数:

             pthread_rwlock_init函数

             pthread_rwlock_destroy函数

             pthread_rwlock_rdlock函数 

             pthread_rwlock_wrlock函数

             pthread_rwlock_tryrdlock函数

             pthread_rwlock_trywrlock函数

             pthread_rwlock_unlock函数

    以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。  

             pthread_rwlock_t类型   用于定义一个读写锁变量。

             pthread_rwlock_t rwlock;

    使用场景:适用于对数据结构读的次数远大于写

    四、条件变量:

             条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

        优点:相对mutex而言,减少不必要的竞争

    如果直接使用mutex,除了生产者、消费者之间需要竞争互斥量以外,消费者之间也需要竞争互斥量,但是如果汇聚(链表)中没有数据,消费者之间惊蛰互斥锁是没有意义的。有了条件变量机制以后,只有生产者完成生产,才回引起消费者之间的竞争。提高程序效率

    主要应用函数:

             pthread_cond_init函数

             pthread_cond_destroy函数

             pthread_cond_wait函数

             pthread_cond_timedwait函数

             pthread_cond_signal函数      唤醒(至少)一个阻塞在条件变量上的线程

             pthread_cond_broadcast函数   唤醒全部阻塞在条件变量上的线程

    以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

             pthread_cond_t类型      用于定义条件变量

             pthread_cond_t cond;

    pthread_cond_wait函数
    作用:阻塞等待一个条件变量
    
        int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    
    函数作用:
    1、阻塞等待条件变量cond(参1)满足 
    2、释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
      //1.2.两步为一个原子操作。
    3、当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

     生产者消费者模型,见下一节

    pthread_cond_timedwait函数
    限时等待一个条件变量
    
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
    
             参3:参看man sem_timedwait函数,查看struct timespec结构体。
    
             struct timespec {
    
                    time_t tv_sec;          /* seconds */long   tv_nsec;      /* nanosecondes*/ 纳秒
    
             }                                                                        
    
      形参abstime:绝对时间。                                                                                     
    
    如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。   
    
            struct timespec t = {1, 0};
    
            pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)
    
     正确用法:
    
       time_t cur = time(NULL); 获取当前时间。
    
      struct timespec t;    定义timespec 结构体变量t
    
       t.tv_sec = cur+1; 定时1秒
    
    pthread_cond_timedwait (&cond, &mutex, &t); 传参                             参APUE.11.6线程同步条件变量小节
    
    在讲解setitimer函数时我们还提到另外一种时间类型:
    
        struct timeval {
    
             time_t      tv_sec;  /* seconds */ 秒
    
             suseconds_t tv_usec; /* microseconds */ 微秒
    
        };

     五、信号量

    可以理解为进化版的互斥锁(1-->N)

      由于互斥锁的粒度比较大,若我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,但无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异

      信号量,是相对这种的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发

    主要应用函数:

             sem_init函数

             sem_destroy函数

             sem_wait函数

             sem_trywait函数  

             sem_timedwait函数      

             sem_post函数

    以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。(注意,它们没有pthread前缀)

             sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。

        sem_t sem; 规定信号量sem不能 < 0。头文件 <semaphore.h>

    信号量基本操作:

    sem_wait:        1. 信号量大于0,则信号量--                (类比pthread_mutex_lock)

               |             2. 信号量等于0,造成线程阻塞

             对应

               | 

    sem_post:     将信号量++,同时唤醒阻塞在信号量上的线程         (类比pthread_mutex_unlock)

    但,由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。

    信号量的初值,决定了占用信号量的线程的个数。

    -------------------------------------------------------------------------------------------------------------------------

    生产者消费者信号量模型(见下章)

    -------------------------------------------------------------------------------------------------------------------------

  • 相关阅读:
    cesium图形上加载图片
    cesium可视化空间数据2
    linux命令之用户和用户组
    YARN应用程序开发和设计流程
    Yarn中几个专用名称
    break、continue、return之间的区别与联系
    kafka的相关操作脚本
    scala函数进阶篇
    scala的基础部分
    视图
  • 原文地址:https://www.cnblogs.com/zyqy/p/10793603.html
Copyright © 2011-2022 走看看