【问题标题】:Is this lock + ManualResetEvent usage thread safe?这个锁 + ManualResetEvent 使用线程安全吗?
【发布时间】:2019-06-13 15:09:55
【问题描述】:

这是一个基于this question 的单独问题。回顾一下,假设我有两个操作计数的函数和一个定期触发的 OnTimer 函数。我的愿望是,如果/当调用 OverwriteCount 时,在计时器函数执行之前不能执行 IncrementCount。

建议的解决方案是:

private int _myCount = 0;
private readonly object _sync = new object();
private ManualResetEventSlim mre = new ManualResetEventSlim(initialState: true);

void IncrementCount()
{
    mre.Wait(); // all threads wait until the event is signaled

    lock (_sync)
    {
        _myCount++;
    }
}

void OverwriteCount(int newValue)
{
    lock (_sync)
    {
        mre.Reset(); // unsignal the event, blocking threads
        _myCount = newValue;
    }
}

void OnTimer()
{
    lock (_sync)
    {
        Console.WriteLine(_myCount);
        mre.Set(); // signal the event
    }
}

ManualResetEventSlim 尝试确保一旦 OverwriteCount() 取消事件信号,对 _myCount 的任何修改都必须等到 OnTimer() 执行。

问题

  1. 假设线程 A 进入 IncrementCount() 并通过事件的 wait() - ManualResetEvent 的初始状态已经发出信号。
  2. 然后线程 B 启动并执行所有 OverwriteCount()。
  3. 然后线程 A 继续获取锁并递增 _myCount。

这违反了我的目标,因为在 OnTimer 运行之前调用 OverwriteCount() 后 _myCount 会发生变化。

Rejected Alternative:我可以在 lock(_sync) 中移动 mre.Wait(),但这会带来死锁风险。如果线程 A 调用 IncrementCount() 并在等待时阻塞,则没有其他线程可以获取锁来释放它。

问题:我是否需要不同的同步原语来实现我的目标?或者,我对线程安全问题是否有误?

【问题讨论】:

  • 您可以在IncrementCount 的锁内再次检查,0-等待只是检查信号状态,如果不正确则退出。 IE。在 IncrementCount 的 lock 语句中添加这个:if (!mre.Wait(0)) return;.

标签: c# .net multithreading concurrency


【解决方案1】:

我认为您只需使用标准 Monitor 和一个附加标志即可实现您的目标。

private readonly object _sync = new object();
private int _myCount = 0;
private bool _canIncrement = true;

void IncrementCount()
{
    lock (_sync)
    {
        // If the flag indicates we can't increment, unlock _sync and wait for a pulse.
        // Use a loop here to ensure that if Wait() returns following the PulseAll() below
        // (after re-acquiring the lock on _sync), but a call to OverwriteCount managed to
        // occur in-between, that we wait again.
        while (!_canIncrement)
        {
            Monitor.Wait(_sync);
        }

        _myCount++;
    }
}

void OverwriteCount(int newValue)
{
    lock (_sync)
    {
        _canIncrement = false;
        _myCount = newValue;
    }
}

void OnTimer()
{
    lock (_sync)
    {
        Console.WriteLine(_myCount);
        _canIncrement = true;
        // Ready any threads waiting on _sync in IncrementCount() above
        Monitor.PulseAll(_sync);
    }
}

【讨论】:

  • 假设 Monitor.Wait 中有多个线程。然后 OnTimer 调用 Monitor.PulseAll。 1. 作为退出 Monitor.Wait 的一部分,其中一个被阻塞的线程可以重新获得锁,我是否正确? 2. 您是否担心(在 IncrementCount 注释中)新线程可能会在 PulseAll 触发和其中一个阻塞线程重新获取锁之间的时间空间内调用 OverwriteCount?
  • @Craig - #1:为了从Wait()返回,线程必须重新获取锁,一次只能有一个线程这样做,所以如果有多个线程在等待,一个将重新获取锁并从Wait()返回,一旦释放锁,下一个等待线程可以重新获取它并从Wait()返回,等等。#2:是的,完全正确。
猜你喜欢
  • 2012-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多