阻塞一个并发集合,如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);

怎么做呢?

阻塞一个并发集合,如ConcurrentBag或BlockingCollection

我怀疑这比仅仅用lock关键字包装几行代码要困难得多。原因是您必须用锁包装ConcurrentBag的所有访问。如果你这样做了,那么它就不再是"并发的"了。我认为你想要的是一种暂时挂起集合的并发行为,以支持序列化行为,然后在完成一个特殊的保护操作后再次恢复其并发行为的方法。

朴素的方法是创建自己的集合,通过在内部使用它作为底层集合来提供与ConcurrentBag相同的操作。然后提供SuspendConcurrentResumeConcurrent操作,通过设置和重置标志(可能通过Interlocked.CompareExchange方法)来切换集合的并发行为。然后,代码将使用该标志来确定是直接将AddTryPeekTryTake操作转发给底层集合,还是在转发之前使用硬锁。我想最后你会发现代码非常复杂,很难正确编写。根据个人经验,您现在设想的任何微不足道的实现都可能是错误的,效率低下的,或者在您希望的时候不能完全并发。

我的建议是找出一种方法来重构你的代码,使这个需求消失。

编辑:

从你的一个评论来看,似乎你想要一个类似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);
}