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

GitHub - DoasIsay/XCoroutine: 一个使用C/C++基于epoll实现的高性能的stackfull协程库,通过HOOK阻塞的系统调用,网络IO事件,协程间的同步事件及定时事件驱动协程的调度,通过汇编完成协程的高速切换,支持海量协程创建,支持协程的动态跨线程负载均衡调度,优先级调度,支持协程的栈上溢出检测及协程的signal信号处理机制,提供不同线程间协程同步协作的互斥量mutex,读写锁,条件变量cond,信号量sem,countDownLatch及用于数据共享的channel等等,总之很好玩,,,

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

我怀疑是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的实现有问题,代码如下

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

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
orchid是一个构建于强大的boost基础上的C ,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型。 协程,顾名思义,协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的抢占式线程相比,有2个主要的优点: 与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常的小。 green化 术语“green化”来自于python下著名的协程greenlet,指改造IO对象以能和协程配合。某种意义上,协程与线程的关系类似与线程与进程的关系,多个协程会在同一个线程的上下文之中运行。因此,当出现IO操作的时候,为了能够与协程相互配合,只阻塞当前协程而非整个线程,需要将io对象“green化”。目前orchid提供的green化的io对象包括: tcp socket(还不支持udp) descriptor(目前仅支持非文件类型文件描述符,如管道和标准输入/输出,文件类型的支持会在以后版本添加) timer (定时器) signal (信号) chan:协程间通信 chan这个概念引用自golang的chan。每个协程一个独立的执行单元,为了能够方便协程之间的通信/同步,orchid提供了chan这种机制。chan本质上是一个阻塞消息队列,后面我们将看到,chan不仅可以用于同一个调度器上的协程之间的通信,而且可以用于不同调度器上的协程之间的通信。 多核 建议使用的scheduler per cpu的的模型来支持多核的机器,即为每个CPU核心分配一个调度器,有多少核心就创建多少个调度器。不同调度器的协程之间也可以通过chan来通信。协程应该被创建在哪个调度器里由用户自己决定。 进一步信息请阅读doc目录下tutorial。如果您发现任何bug或者有任何改进意见,请联系ioriiod0@gmail.com 标签:orchid
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值