work-stealing-queue设计
- 每个线程使用动态数组作为准备调度的deque 来存放任务(也就是work-stealing-queue)
- 每个线程各自维护这样一个deque,将该deque当作stack使用,自己线程内的deque只能通过push和pop访问栈底(bottom)
- 如果其他线程的deque为空,可以调用其他线程的steal操作,从其他线程维护的deque的顶部(top)偷取任务并执行,一般的实现中,对这个队列的偷取是随机选择的。
期望表现
推入deque的任务要么被同一线程以相反的顺序执行(对于本线程来说相当于一个stack),要么被另一个线程窃取。
PS: 这种设计的巧妙性在于,除非deque中的任务数小于等于1, 否则本线程中push 和 pop 是不存在多线程竞态条件的,steal 也只会有一个CAS的开销。
正确性的四个准则
- 任务按相反的顺序执行
- 只有被push过的任务才能被pop或者被steal
- 被push进deque的任务,只会被pop或者steal一次(唯一性)
- 给定有限次数的push操作,所有被push的任务最重都会被pop(或者steal)一次。
设计优势
- 对于任何给定的deque,push和pop操作单线程执行。并发只能在所有者线程的一次push或pop执行与不同线程中的一次或多次窃取执行之间发生。
- pop通过更新bottom来保留Task,steal通过更新top来保留Task,只有当deque仅有一个Task的时候才会有竞态条件(通过CAS指令解决冲突)
- pop 和 steal 其中任意一个,能够看到deque大小的一致性视图就足够保证正确性。如果是pop,这将迫使执行CAS;如果是steal,将确保返回值为空(没steal到)。
- 正在被steal的Task,会被CAS保护,仅会被steal一次。CAS保证对一个Task的独占,不会造成steal和steal之间的问题,同时,如果CAS失败,也不会改变deque的状态。
参考文献
https://www.di.ens.fr/~zappa/readings/ppopp13.pdf