阻塞一个并发集合,如ConcurrentBag或BlockingCollection
本文关键字:集合 ConcurrentBag BlockingCollection 并发 一个 | 更新日期: 2023-09-27 18:09:55
我想执行两条语句访问一个ConcurrentBag
,其间没有任何其他线程访问它。例如:
ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
concurrentBag.Add(1);
int check = 0;
//Somehow block so that the following two statements will
//be executed without any other thread accessing concurrentBag.
concurrentBag.TryPeek(out check);
if (check == 2) concurrentBag.Add(3);
怎么做呢?
我怀疑这比仅仅用lock
关键字包装几行代码要困难得多。原因是您必须用锁包装对ConcurrentBag
的所有访问。如果你这样做了,那么它就不再是"并发的"了。我认为你想要的是一种暂时挂起集合的并发行为,以支持序列化行为,然后在完成一个特殊的保护操作后再次恢复其并发行为的方法。
朴素的方法是创建自己的集合,通过在内部使用它作为底层集合来提供与ConcurrentBag
相同的操作。然后提供SuspendConcurrent
和ResumeConcurrent
操作,通过设置和重置标志(可能通过Interlocked.CompareExchange
方法)来切换集合的并发行为。然后,代码将使用该标志来确定是直接将Add
、TryPeek
和TryTake
操作转发给底层集合,还是在转发之前使用硬锁。我想最后你会发现代码非常复杂,很难正确编写。根据个人经验,您现在设想的任何微不足道的实现都可能是错误的,效率低下的,或者在您希望的时候不能完全并发。
我的建议是找出一种方法来重构你的代码,使这个需求消失。
编辑:从你的一个评论来看,似乎你想要一个类似CAS的操作,能够根据是否已经发生的事情做出决定。有几种选择,但其中一种可能最适合您的需要,那就是使用提供TryAdd
方法的ConcurrentDictionary
类。如果键不存在,TryAdd
将返回true。如果键确实存在,则返回false。许多线程可以使用相同的键同时执行此方法,但只有一个线程会得到真正的响应。然后,您可以使用if
语句来根据返回值控制程序流。
如果您想要无条件地阻止任何其他线程访问某个对象,那么您根本不能这样做。也就是说,如果你有一个对象:
public Object foo = new Object();
没有你可以在你的线程中做什么来阻止其他线程访问foo
。正如您所指出的,锁不起作用,因为锁是一个合作互斥装置。除了约定之外,没有什么可以阻止某人编写忽略锁而访问对象的代码。
你可以将对象包装在一个强制互斥的类中,但随后你所做的只是添加一个锁或类似的东西。即使这样,您也必须创建一个特殊的方法来实现您正在寻找的语义(即窥视和添加)。即使这样也不是万无一失的,因为有人可以使用反射直接访问底层数据结构。
你的窥视和添加操作有点不寻常。我不太了解您的应用程序,无法给出肯定的答复,但在大多数情况下,有一种更传统的方式来实现您想要的功能。对于ConcurrentBag
这样的东西尤其如此,Peek
将根据访问它的线程给出不同的结果。
static object syncLock = new object();
ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
concurrentBag.Add(1);
int check = 0;
lock(syncLock)
{
concurrentBag.TryPeek(out check);
if (check == 2) concurrentBag.Add(3);
}