锁定线程计数
Posted
技术标签:
【中文标题】锁定线程计数【英文标题】:Counting of threads locked 【发布时间】:2017-06-13 20:09:19 【问题描述】:代码如下:
private int _count = 0;
public bool Ongoing
get
return _count > 0;
public void Method1(object param)
new Thread(new ParameterizedThreadStart(Method2)).Start(param);
private void Method2(object param)
_count++;
lock (_lock)
// Something
_count--;
变量_count,你可以猜到,用于计算有多少线程被锁定。它被初始化为 0 并且只在这个方法内部被修改,我用它来知道这个类是否在做某事。
现在的问题是:有时 _count 低于 0。就像 _count++ 有时会被忽略。
这种情况很少发生,大约每 500 次我启动此方法时发生一次,甚至更少。
我应该将 _count 声明为 volatile 吗?
【问题讨论】:
Interlocked.Increment Method. @JohnnyMopp 你到底为什么要同时使用两种不同的同步方法。代码已经开锁了。 @Servy 因为增量发生在锁之外。 @JohnnyMopp 是的,它不应该。 这就是问题。 还有一个:How to get the amount of threads waiting to enter a lock? 【参考方案1】:您需要确保以原子方式应用操作并且不会缓存值。要实现第一个,您必须使用Interlocked.Increment
和Interlocked.Decrement
,第二个将字段标记为volatile
:
private volatile int _count = 0;
public bool Ongoing
get
return _count > 0;
public void Method1(object param)
new Thread(new ParameterizedThreadStart(Method2)).Start(param);
private void Method2(object param)
Interlocked.Increment(ref _count);
lock (_lock)
// Something
Interlocked.Decrement(ref _count);
【讨论】:
将该字段标记为volatile
并不是重新阅读的最佳方法。在读取之前,在 getter 中使用显式内存屏障会做得更好。
@Douglas 在这种情况下,执行显式内存屏障而不是 Volatile.Read(ref _count)
是否有附加价值?
@KevinGosse:是的。易失性读取在读取之后引入了一个半栅栏,这意味着您在第一次读取时仍然会获得陈旧的值。
@Douglas 虽然如果真的是这样,那么 MSDN 文档是错误的:msdn.microsoft.com/en-us/library/gg712971(v=vs.110).aspxThis value is the latest written by any processor in the computer, regardless of the number of processors or the state of processor cache.
@KevinGosse:MSDN 文档大错特错。 “MSDN 文档指出,使用 volatile 关键字可确保字段中始终存在最新的值。这是不正确的,因为正如我们所见 [在给定示例中],随后写入通过阅读可以重新排序。” (albahari.com/threading/part4.aspx)【参考方案2】:
正如在接受的答案中正确指出的那样,您需要围绕增量和减量操作执行线程同步。这可以通过Interlocked
类来实现。
但是,您还需要使用一些内存同步机制来确保其他线程可以使用最新的值。 volatile
上的 MSDN 文档大错特错:
MSDN 文档指出,使用 volatile 关键字可确保字段中始终存在最新的值。这是不正确的,因为正如我们所见,写入后读取可以重新排序。 (Joseph Albahari)
具体来说,volatile 关键字指示编译器在每次读取该字段时生成一个获取栅栏。但是,acquire-fence 在读取之后 生效,以防止其他读取/写入移动到它之前。这意味着读取本身可能会向上移动,从而允许在第一次读取变量时检索过时的值。
不幸的是,没有干净的方法可以读取具有所需内存屏障的整数,但最近的候选者是冗余的Interlocked.CompareExchange
(请参阅this answer):
private int _count = 0;
public bool Ongoing => Interlocked.CompareExchange(ref _count, 0, 0) > 0;
public void Method1(object param)
new Thread(new ParameterizedThreadStart(Method2)).Start(param);
private void Method2(object param)
Interlocked.Increment(ref _count);
lock (_lock)
// Something
Interlocked.Decrement(ref _count);
【讨论】:
这完全阻止了用户的目的,他想知道有多少线程在等待锁,所以增量和减量必须在锁之外。 @Gusman:你是对的;我完全错过了。我会修复代码。Interlocked.Read
是 Interlocked.CompareExchange(..., 0, 0)
的简写。
@JeroenMostert:Interlocked.Read
在哪个版本的 .NET 上使用 int
?
好吧,可惜没有重载。以上是关于锁定线程计数的主要内容,如果未能解决你的问题,请参考以下文章