再次使用联锁

Posted

技术标签:

【中文标题】再次使用联锁【英文标题】:Again on using interlocked 【发布时间】:2016-02-06 08:56:43 【问题描述】:

我阅读了很多关于这一点的答案,但我还没有找到解决方案。

我有一个带有计数器属性的类,它的缓存值有问题。甚至 volatile 似乎也不起作用:

public class MyClass 
    private Timer _timer;
    private int _threadsCounter = 0;
    public StreamWriter Tracer  get; set; 

    public MyClass() 
        _timer = new Timer(1000.0 * 10);
        _timer.AutoReset = true;
        _timer.Elapsed += new ElapsedEventHandler(OnTimer);
        _timer.Start();
    

    private void OnTimer(object sender, ElapsedEventArgs e) 
        HashSet<Task> taskPool = new HashSet<Task>();
        try 
            if (Tracer != null) Tracer.WriteLine("[0] onTimer start. Current threads counter is 1.", DateTime.Now, _threadsCounter);
            if (_threadsCounter >= 10) return;

            // create parallel tasks
            for (int i = 0; i < 8; i++) 
                // limit on the max num of parallel processing but the counter remains unchanged during this timer event!!!
                if (_threadsCounter >= 10) break;

                var timeout = (30 + i * 2);
                var task = Task.Run(() => 
                        var localCounter = System.Threading.Interlocked.Increment(ref _threadsCounter);
                        try 
                            System.Threading.Thread.Sleep(timeout * 1000);
                        
                        finally 
                            System.Threading.Interlocked.Decrement(ref _threadsCounter);
                        
                    );
                taskPool.Add(task);
            

        
        finally 
            if (Tracer != null) 
                Tracer.WriteLine("[0] onTimer end. Created 1 tasks. Current threads counter is 2.", DateTime.Now, taskPool.Count, _threadsCounter);
        
    

嗯,似乎 onTimer 缓存了 _threadsCounter 变量,因为输出是:

[14:10:47] onTimer start. Current threads counter is 0.
[14:10:47] onTimer end. Created 8 tasks. Current threads counter is 0.

[14:10:57] onTimer start. Current threads counter is 8.
[14:10:57] onTimer end. Created 8 tasks. Current threads counter is 8.

[14:11:07] onTimer start. Current threads counter is 16.
[14:11:07] onTimer end. Created 0 tasks. Current threads counter is 16.

[14:11:17] onTimer start. Current threads counter is 15.
[14:11:17] onTimer end. Created 0 tasks. Current threads counter is 15.

[14:11:37] onTimer start. Current threads counter is 4.
[14:11:37] onTimer end. Created 8 tasks. Current threads counter is 4.

[14:11:47] onTimer start. Current threads counter is 8.
[14:11:47] onTimer end. Created 8 tasks. Current threads counter is 8.

[14:11:57] onTimer start. Current threads counter is 16.
[14:11:57] onTimer end. Created 0 tasks. Current threads counter is 16.

为什么我要到 16 岁? 我通过更改代码解决了这个问题:

var localCounter = _threadsCounter;
...
if ((localCounter + taskPool.Count) >= 10) break;

但是为什么会有这种行为呢?

【问题讨论】:

看起来您正在循环中检查_threadsCounter,但更改_threadsCounter 的实际代码在您的任务中,该任务正在不同的线程上运行。因此,您的 _threadsCounter 当然不会在下次循环时增加。您在上一次迭代中开始的任务可能还没有开始。 也许你已经幸运地得到了“修复”。确实,如果_threadsCounter 是多个线程之间的共享资源,您需要同步访问此共享资源的所有读/写。 @ChrisO:不走运,taskPool.Count 实际上是在将任务添加到池中时更新的。换句话说,在循环中。而_threadsCounter 不是。只有当操作系统开始实际启动这些线程时(可能是在循环完成之后),它才会更新。 @MattBurland 你是对的!我不认为任务仍然悬而未决。谢谢 @MattBurland 知道了,谢谢。仍然需要在 _threadsCounter 上进行同步访问,这在两个地方缺失。 【参考方案1】:

Task.Run 不会立即启动任务。它将任务添加到线程池队列并返回。

在您的情况下,整个for 循环在新任务开始运行之前执行,因此没有任何变化_threadsCounter。这就是 volatile 没有帮助的原因。

【讨论】:

【参考方案2】:

您正在有效地测试实际开始并达到递增计数器的任务数。这需要一点时间 - 所以基本上你正在创建所有 8 个任务并启动它们,然后它们正在增加计数器......到那时注意到你有更多的为时已晚超过 10 个。

更好的解决方案是在开始任务之前增加计数器

// Increment the counter in expectation of starting a task
var localCounter = Interlocked.Increment(ref _threadsCounter);
if (localCounter >= 10)

    // Ah, we're not going to start a task after all, so undo
    // the increment
    Interlocked.Decrement(ref _threadsCounter);
    break;

else 

    // Start a task, which will decrement the counter at the end.
    // (You could add the "decrement" bit as a continuation, even...)

【讨论】:

以上是关于再次使用联锁的主要内容,如果未能解决你的问题,请参考以下文章

ZooKeeper 分布式锁 Curator 源码 05:分布式读写锁和联锁

我可以使用联锁操作来更新多个值以避免锁定关键部分/互斥锁吗?

为啥 setMouseDown(false) 不能联锁回调函数?

联锁平均值 (CAS) 不适用于 HLSL

WaitForSingleObject 与联锁*

混合锁和联锁操作是不是安全?