线程同步

线程同步

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

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

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

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

数据混乱原因:

         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的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。

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

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

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

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

 

posted @ 2019-04-30 17:39  砍柴人Ryan  阅读(186)  评论(0编辑  收藏  举报