锁定线程计数

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.IncrementInterlocked.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.ReadInterlocked.CompareExchange(..., 0, 0) 的简写。 @JeroenMostert:Interlocked.Read 在哪个版本的 .NET 上使用 int 好吧,可惜没有重载。

以上是关于锁定线程计数的主要内容,如果未能解决你的问题,请参考以下文章

当我们已经锁定它时,如何再次锁定折返锁有用? [重复]

Python之多线程。

如何找到信号量未锁定的原因

信号量 也是同步锁,可用来控制线程的并发数

java 多线程-可重入锁

通知线程是不是可以在被通知线程等待锁定之前锁定?