线程同步之条件变量使用手记

由来:

最近一直在想怎么高效率的在IO线程接收到数据时通知逻辑线程(基于线程池)工作的问题,像网络编程的服务器模型的一些模型都需要用到这个实现,下面我这里简单的罗列一个多线程的网络服务器模型

半同步/半异步(half-sync/half-async):

许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。

对于上面罗列的这种模型,本文讨论的问题是当领班接到客人时,如何高效率的通知侍应生去服务顾客.

在我们使用很广泛的线程池实现中,也会有一样的问题

方法实现:

1.使用锁+轮询

使用这种方法可以很简单的实现,但是会有一定的性能消耗,其还有一个点要好好把握,就是一次轮询没有结果后相隔多久进行下一次的轮询,间隔时间太短,消耗的CPU资源较多,间隔时间太长,不能很及时的响应请求。这就相当于上面的这个例子,侍应生时不时的取询问领班有没有顾客到来

2.使用条件变量的线程同步

线程条件变量pthread_cond_t

线程等待某个条件

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 
通知函数

通知所有的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

只通知一个线程
int pthread_cond_signal(pthread_cond_t *cond);

正确的使用方法

  pthread_cond_wait用法

pthread_mutex_lock(&mutex);

while(condition_is_false)

{

pthread_cond_wait(&cond,&mutex);

}

condition_is_false=true;  //此操作是带锁的,也就是说只有一个线程同时进入这块

pthread_mutex_unlock(&mutex);

pthread_cond_signal用法:

pthread_mutex_lock(&mutex);

condition_is_false=false;

pthread_cond_signal(&cond)

pthread_mutex_unlock(&mutex)

我刚初用的时候,觉得非常的奇怪,为什么要这样用,加了mutex后还需要一个condition_is_false变量来表示有没有活干。其实这样子的一个操作主要是为了解决“假激活”问题,因为我么您这里的使用场景,只需要激活一个线程,因为一个线程干一个活,而不是多个线程干一个活,所以为了避免线程被激活了,但实际又没有事情干,所以使用了这么一套机制。

实际上,信号和pthread_cond_broadcast是两个常见的导致假唤醒的情况。假如条件变量上有多个线程在等待,pthread_cond_broadcast会唤醒所有的等待线程,而pthread_cond_signal只会唤醒其中一个等待线程。这样,pthread_cond_broadcast的情况也许要在pthread_cond_wait前使用while循环来检查条件变量。

来个例子:

事实上上面的例子无论是使用pthread_cond_signal还是pthread_cond_broadcast,都只会打印Thread awake, finish work5次,大家可能会觉得非常奇怪,但是实际情况就是这样的。 为了明白其pthread_cont_wait内部干了什么工作,有必要深入一下其内部实现。

关于其内部实现伪代码如下:

 1 pthread_cond_wait(mutex, cond):
 2     value = cond->value; /* 1 */
 3     pthread_mutex_unlock(mutex); /* 2 */
 4     pthread_mutex_lock(cond->mutex); /* 10 */    pthread_cond_t自带一个mutex来互斥对waiter等待链表的操作
 5     if (value == cond->value) { /* 11 */    检查一次是不是cond有被其他线程设置过,相当于单例模式的第二次检测是否为NULL
 6         me->next_cond = cond->waiter;
 7         cond->waiter = me;//链表操作
 8         pthread_mutex_unlock(cond->mutex);
 9         unable_to_run(me);
10     } else
11         pthread_mutex_unlock(cond->mutex); /* 12 */
12     pthread_mutex_lock(mutex); /* 13 */
13
14 pthread_cond_signal(cond):
15     pthread_mutex_lock(cond->mutex); /* 3 */
16     cond->value++; /* 4 */
17     if (cond->waiter) { /* 5 */
18         sleeper = cond->waiter; /* 6 */
19         cond->waiter = sleeper->next_cond; /* 7 */        //链表操作
20         able_to_run(sleeper); /* 8 */    运行sleep的线程,即上面的me
21     }
22     pthread_mutex_unlock(cond->mutex); /* 9 */

pthread_cond_broadcast虽然能够激活所有的线程,但是激活之后会有mutex锁,也就是说他的激活是顺序进行的,只有第一个激活的线程调用pthread_mutex_unlock(&mutex)后,后一个等待的线程才会继续运行.因为从pthread_cond_wait(&cond,&mutex)到pthread_mutex_unlock(&mutex)区间是加的独占锁,从wait激活后的第一个线程占用了这个锁,所以其他的线程不能运行,只能等待。所以当第一个被激活的线程修改了condition_is_false后(上面测试代码的workToDo),接着调用pthread_mutex_unlock(&mutex)后,此时其他等待在cond的一个线程会激活,但是此时condition_is_false已经被设置,所以他跑不出while循环,当调用pthread_cond_wait时,其内部pthread_mutex_unlock(mutex)调用会导致另一个在它后面的等待在cond的线程被激活。  

所以,通过这种方式,即便是误调用了pthread_cond_broadcast或者由于信号中断的原因激活了所有在等待条件的线程,也能保证其结果是正确的。

另外说一句题外话,很多人写的基于条件变量线程同步的框架,说自己是无锁的,其实这是不对的,只是内部锁的机制在pthread_cond_wait实现了而已,其还是基于互斥锁的实现。真正想要达到无锁的可以关注一下lockfree相关的CAS算法,其内部使用一个intel CPU的cmpxchg8指令完成的,其实这种实现个人认为和传统锁相比只是一个非阻塞锁和阻塞锁的区别。

时间: 2024-10-28 06:09:54

线程同步之条件变量使用手记的相关文章

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

线程—同步之条件变量

条件变量:允许线程阻塞等待另一个线程发送信号唤醒.条件变量被用来阻塞一个线程,当条件不满足时,线程解开相应的互斥锁并等待条件发生变化.如果其他线程改变了条件变量,并且使用条件变量换型一个或多个正被此条件变量阻塞的线程.这些线程将重新锁定互斥锁并重新测试条件是否满足.条件变量被用来进行线程间的同步. thread 1 con = threading.Condition()    #创建条件变量 while True: do something con.acquire()    #获取锁 con.n

线程同步(条件变量、信号量)以及死锁

死锁:指两个或两个以上进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待现象,若无外力作用,它们都将无法继续推进下去. 例:交叉死锁:线程1获得了锁1,线程2获得了锁2,此时线程1调用lock想获得锁2,需挂起等待线程2释放锁2,而线程2也想获得锁1,也需挂起等待线程1释放锁1,此时两个线程都挂起等待 产生死锁的四个必要条件: (1):互斥条件(一个资源每次只能被一个进程或线程使用) (2):请求与保持条件(一个进程或线程因请求资源而阻塞时,对已获得的资源不释放) (3):不剥夺条件(此

线程同步之——条件变量

一.生产消费模型:我们可以用条件变量来实现线程之间的同步,利用一个生产消费模型具体的实现同步.生产消费模型可以简单地称为3,2,1模型(即3种关系,2个对象,1个场所),同时还需注意以下3点: 1.生产者和消费者是同步互斥关系: 2.生产者和生产者是互斥关系: 3.消费者和消费者是互斥关系. 二.条件变量的理解:线程A需要等某个条件成才能继续往下执,现在这个条件不成,线程A就阻塞等待,线程B在执过程中使这个条件成了,就唤醒线程A继续执. 在pthread库中通过条件变量(Condition Va

linux线程同步(2)-条件变量

一.概述                                                    上一篇,介绍了互斥量.条件变量与互斥量不同,互斥量是防止多线程同时访问共享的互斥变量来保护临界区.条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行.通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行. 条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个

3线程同步:条件变量

1 条件变量 条件变量给多个线程提供了一个汇合的场所. 依赖的头文件 #include<pthread.h> 函数声明 定义分配条件变量 pthread_cond_t cond =PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t*restrict cond, const pthread_condattr_t *restrict attr); 名称: pthread_cond_init 功能: initialize co

四十一、Linux 线程——线程同步之条件变量

41.1 概念 41.1.1 条件变量的介绍 互斥锁的缺点是它只有两种状态:锁定和非锁定 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足 条件变量内部是一个等待队列,放置等待的线程,线程在条件变量上等待和通知,互斥锁用来保护等待队列(对等待队列上锁),条件变量通常和互斥锁一起使用 条件变量允许线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生变化.一旦其它的某个线程改变了条件,可唤醒一个或多个阻塞的线程 具体的判断条件还需用户给出 条件变量数据类

四十二、Linux 线程——线程同步之条件变量之线程状态转换

42.1 线程状态转换 42.1.1 状态转换图 42.1.2 一个线程计算,多个线程获取的案例 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include <unistd.h> 5 6 /** 两个线程定义的共享资源 */ 7 typedef struct { 8 int res; 9 int counter; ///< 用于统计获取结果线程的数量 10 p

生成者消费者(线程同步,互斥,条件变量)

#include <unistd.h> #include "stdio.h" #include <stdlib.h> #include <pthread.h> #define N_CONSUMER 3 //消费者数量 #define N_PRODUCER 2 //生产者数量 #define C_SLEEP 1 //控制 consumer 消费的节奏 #define P_SLEEP 1 //控制 producer 生产的节奏 pthread_t cti