【超越条件变量】多生产者,单消费者,最节约唤醒API调用的方案。2020-5-29升级版。

三态解决多生产者(多任务发布线程) 唤醒 单消费者(具体工作线程)的系统调用消耗。
以下内容全是我superzmy独立自行发明的,但不保证和其他方案雷同,因为我完全没看到过类似的。
这篇文章讲的是在传统唤醒方案嫌太慢的情况下,使用无锁方案大幅度降低系统调用提高性能的方案,在阅读之前,请保证你自己对唤醒工作线程的背景和常见解决方法有所了解。

消费者:
【超越条件变量】多生产者,单消费者,最节约唤醒API调用的方案。2020-5-29升级版。_第1张图片

生产者:
准备数据
放入队列
Test:根据状态值尝试以下cas

  1. 工作态(无cas):OK,消费者本来就在工作态,所以数据必将被处理
  2. 检查态:消费者可能已经在Check之前或之后,生产者此时应
    或【a方案:生产者堵塞直到脱离检查态以后再重试】
    (可进一步优化使多个生产者只有一个堵塞)
    或【b方案:生产者无脑唤醒消费者并跳转到工作态】
    (可能造成多余的唤醒API调用)
    或【c方案:生产者将检查态+1从检查态0变成检查态1】
    (当消费者在即将退出检查态x时,发现当前最新状态是检查态y,
    且未发现有可处理数据时,应重新检查,因为检查态值递增说明应有新数据到来?或者假定有新数据直接返回工作态
    已发现任务时,置为工作态
    当消费者在即将退出检查态x时,发现当前最新状态是检查态x,那么从cas(检查态→空闲态)
  3. 空闲态→工作态:OK,新的状态是工作态(且是由本生产者转变的),因此现在调用API唤醒生产者,并转变成工作态。
  4. 退出态(无cas):消费者不干了,这里爱咋办咋办,也许可以新建一个消费者,或者自己清理数据。

a方案,是有概率堵塞的lock的(因此在linux下要临时关闭线程调度),但能保证没有多余的“唤醒”;另外如果这个操作不是生产者,而是【关闭系统】,那么必须采用这种方案,才能干净安全关闭系统。特别是所谓的唤醒是有副作用的时候,比如如果唤醒方法是是在actor模式里投递一个任务,这可能会导致野指针。
b方案,生产者不会堵塞,生产者这个过程是waitfree的:只尝试一次cas不论成功或失败,最后可能再进行一次原子赋值。(虽然生产者始终唤醒业是waitfree,但这个唤醒还是略显沉重)
c方案,似乎最好

假定选择c方案,全过程是这样的:(当cas失败时要重新读取状态值并重试)

生产者投递任务:
	是退出态?报错或略过
	投递数据,读取状态值,根据情况进行如下cas之一:
	cas(检查态x→检查态x+1)
		如果cas成功则结束过程
		如果失败
			新状态是检查态y且y>x,那么也当做cas成功,结束过程
			新状态是空闲态,那么走下面的cas(空闲态→工作态)
	cas(空闲态→工作态)成功则调用唤醒API
		如果失败,说明中间发生了很多故事,但之前的数据一定有人管,所以结束
	工作态(无cas)结束
	退出态(无cas)撤销数据投递或略过
---------------------------------------
消费者(初始化为检查态0):
	检查态x-检查有无数据
		有数据?
			则set(任意→工作态)转工作态
			无数据,仍然是检查态x?
				则cas(检查态x→空闲态或退出态)
				是另一个检查态y?则 set(任意→工作态)转工作态
				是工作态?转工作态
	工作态,处理,然后set(任意→检查态0)转检查态
---------------------------------------

分析生产者:
空闲时, 只有一个生产者能成功进行空闲态→工作态
检查态时,生产者最多一次cas失败后再cas一次必定结束。
每个生产者投递时0~2次cas原子操作,并且仅有空闲态→工作态成功的那个生产者调用唤醒API。出现二次cas的情况概率极低。

分析消费者:
处理完数据后,先set检查态0,然后检查数据,然后仅当检查数据为空时,后续需要cas。全程需要0~1次cas。

该系统在极高生产消费频率时,几乎不调用任何系统API,甚至不调用任何cas,要多快,有多快。并且保证唤醒的片段是伪并行的(仅cas锁总线的粒度)
对比互斥量+条件变量,每次投递任务时都需要加锁,必然需要不止一次cas,且加锁解锁中是串行的还有if判断,条件变量的调用也涉及系统调用、线程调度,慢!

注意,消费者在设置为空闲态并即将休眠自身时,不可使用无状态条件变量并判断状态值,而应该使用有状态的信号量(可以用互斥量+条件变量+计数变量,组合得到) ,因为这个三态模式根本就没用到互斥量。若使用无状态的条件变量和互斥量却没有另外的计数变量,那么在设置为空闲态和条件变量堵塞之间,生产者的notify会被丢弃,而互斥量却不能防止状态值的修改,更遭的是由于三态状态值的保护,消费者将永不再次调用唤醒,所以会产生无法唤醒。

另一套唤醒处理方案也挺好,而且是lock-free,但是会有多余的调用唤醒API的可能性:
生产者: a增加任务数,b读取空闲消费者数 c判断是否全忙(0) d全忙?不唤醒
消费者: 1增加空闲消费者数 2读任务数 3判断任务数 4无任务?休眠 5减少消费者数 6处理数据
https://github.com/Cyinx/einx/blob/master/queue/cond_queue.go
据说是从c#concurrentqueue里得来的思路

你可能感兴趣的:(技术,生产者,唤醒)