一个C/C++协程库的思考与实现之协程的互斥量与条件变量

https://github.com/DoasIsay/ToyCoroutine

在为ToyCoroutine的协程实现互斥量与条件变量时,测试过程中竟然死锁了,代码如下

一个C/C++协程库的思考与实现之协程的互斥量与条件变量

我怀疑是producer,consumer使用了同一个条件变量进行协作导致的,测试时刚好创建了2个consumer协程,1个producer协程,当consumer1协程与producer协程都cond.wait在同一个条件变量时,由于调度原因consumer2调用cond.signal如果每次唤醒的都是consumer1,在最后一次consumer1被唤醒后数据已被consumer2消费完队列为空,consumer1也会cond.wait,此时两个consumer协程与producer协程相互等待产生死锁?

显然这种情况是不会出现的,因为条件变量的waitQue是一个先进先出的队列,在队列为空前producer协程一定会被唤醒,因此使用一个条件变量也是可以的,只不过有点小问题,比如consumer本来是要唤醒producer的,但有可能会唤醒另一consumer

但是使用同一个条件变量确实会产生死锁,当协程的个数大于队列大小时就会产生死锁,比如当队列大小为10,有20个consumer协程时,就会出现直到队列为空,producer协程都不会被唤醒

所以就算每次使用broadcast唤醒所有被阻塞的协程后仍然会死锁,最后通过gdb调试及分析代码发现是由于Mutex的实现有问题,代码如下

一个C/C++协程库的思考与实现之协程的互斥量与条件变量

一个C/C++协程库的思考与实现之协程的互斥量与条件变量

最初的实现waitQue是线程安全的,是没有guard锁的,这里使用线程安全的waitQue没有意义

条件变量的实现也同样简单如下

一个C/C++协程库的思考与实现之协程的互斥量与条件变量

在生产与消费模式下条件变量与互斥量的正确使用方式如下

一个C/C++协程库的思考与实现之协程的互斥量与条件变量

Mutex mutex

Cond condP

Cond condC

生产者

mutex.lock()

condP.wait(mutex)

mutex.unlock()

condC.signal()

消费者

mutex.lock()

condC.wait(mutex)

mutex.unlock()

condP.signal()

cond.wait(mutex)一定要放在mutex.lock()与mutex.unlock()间,因为cond.wait(mutex)会调用mutex.unlock()然后等待被唤醒,在被唤醒后再调用mutex.lock()

对于cond.signal()的位置放在mutex.unlock()前与后都没关系,Cond本身就是线程安全的,不过放在mutex.unlock()后会更好

如果放在mutex.unlock()前,当消费者condP.signal()唤醒生产者时,会出现生产者被唤醒但mutex.lock()失败的情况,此时生产者又要等待消费者mutex.unlock()后才能继续运行,这无形中提高了锁冲突,影响性能

同样适用于linux线程的互斥量pthread_mutex_t与条件变量pthread_cond_t