我正在学习Michael& Scott的无锁队列算法,并试图用C ++实现它。
但我在我的代码中制作了一个竞赛,并认为算法中可能存在竞争。
我在这里阅读了这篇论文: Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms 原始的Dequeue伪代码如下:
dequeue(Q: pointer to queue_t, pvalue: pointer to data type): boolean
D1: loop // Keep trying until Dequeue is done
D2: head = Q->Head // Read Head
D3: tail = Q->Tail // Read Tail
D4: next = head.ptr->next // Read Head.ptr->next
D5: if head == Q->Head // Are head, tail, and next consistent?
D6: if head.ptr == tail.ptr // Is queue empty or Tail falling behind?
D7: if next.ptr == NULL // Is queue empty?
D8: return FALSE // Queue is empty, couldn't dequeue
D9: endif
// Tail is falling behind. Try to advance it
D10: CAS(&Q->Tail, tail, <next.ptr, tail.count+1>)
D11: else // No need to deal with Tail
// Read value before CAS
// Otherwise, another dequeue might free the next node
D12: *pvalue = next.ptr->value
// Try to swing Head to the next node
D13: if CAS(&Q->Head, head, <next.ptr, head.count+1>)
D14: break // Dequeue is done. Exit loop
D15: endif
D16: endif
D17: endif
D18: endloop
D19: free(head.ptr) // It is safe now to free the old node
D20: return TRUE // Queue was not empty, dequeue succeeded
在我看来,比赛是这样的:
head.ptr->next
,但由于线程1已释放head.ptr
,因此发生崩溃。我的C ++代码总是在D4上为线程1崩溃。
任何人都可以指出我的错误并给出一些解释吗?
答案 0 :(得分:9)
希望没有人在没有透彻分析的情况下投入生产。
答案 1 :(得分:2)
实际上,这是自MS队列的作者之一Maged Michael引入危害指针[1]以来多年来一直在研究的无阻塞内存回收问题。
危险指针允许线程保留块,以便其他线程在完成之前不会真正回收它们。但是,这种机制会带来不小的性能开销。
还有很多基于纪元的回收变体,例如RCU [2,3],最近还有基于间隔的回收(IBR)[4]。它们通过保留时间避免了无用后使用,并且比危险指针要快。据我所知,基于纪元的回收被广泛用于处理此问题。
您可以查看下面提到的这些论文以获取更多详细信息。 基于时间间隔的内存回收的论文讨论了很多背景。
这是非阻塞数据结构中的一个普遍问题,我们通常不将其视为数据结构本身的错误-毕竟,它仅在使用手动内存管理(如C / C ++)的语言中发生,而没有在Java之类的公司中(BTW,Michael&Scott Queue在Java Concurrency中已被采用多年)。
参考:
[1] 危险指针:无锁对象的安全内存回收,Maged M. Michael,IEEE并行和分布式系统事务,2004年。
[2] 用于无锁同步的内存回收性能,Thomas E. Hart等人,《并行与分布式计算杂志》,2007年。
[3] 阅读副本更新,Paul E. McKenney等人,渥太华Linux研讨会,2002年。
[4] 基于时间间隔的内存回收,Haosen Wen等人,第23届ACM SIGPLAN并行编程原理和实践研讨会(PPoPP)的会议记录,