简体   繁体   English

这个锁+ ManualResetEvent使用线程安全吗?

[英]Is this lock + ManualResetEvent usage thread safe?

This is a separate question based off of this question . 这是基于该问题的单独问题 To recap, say I have two functions that manipulate a count, and an OnTimer function that fires at a regular interval. 回顾一下,假设我有两个操作计数的函数,以及一个定期触发的OnTimer函数。 My desire is that if/when OverwriteCount is called, IncrementCount can't be executed until the timer function executes. 我的愿望是,如果/当调用OverwriteCount时,在执行计时器函数之前不能执行IncrementCount。

The proposed solution was: 提议的解决方案是:

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
    }
}

The ManualResetEventSlim tries to ensure that once OverwriteCount() unsignals the event, any modifications to _myCount must wait until OnTimer() executes. ManualResetEventSlim尝试确保一旦OverwriteCount()解除事件的信号,对_myCount的任何修改都必须等待OnTimer()执行。

Problem : 问题

  1. Say thread A enters IncrementCount() and passes the event's wait() - the ManualResetEvent's initial state is already signaled. 假设线程A进入IncrementCount()并传递事件的wait()-已经发出ManualResetEvent的初始状态的信号。
  2. Thread B then starts and executes all of OverwriteCount(). 然后,线程B启动并执行所有OverwriteCount()。
  3. Thread A then continues by acquiring the lock and incrementing _myCount. 然后,线程A通过获取锁并递增_myCount来继续。

This violates my goal as _myCount would change after a call to OverwriteCount(), prior to OnTimer running. 这违反了我的目标,因为_myCount将在OnTimer运行之前在调用OverwriteCount()之后更改。

Rejected Alternative : I could move mre.Wait() within lock(_sync) but that poses a deadlock risk. 拒绝的选择 :我可以将mre.Wait()移入lock(_sync)内,但这会带来死锁风险。 If thread A calls IncrementCount() and blocks on the wait, no other threads can acquire the lock to release it. 如果线程A调用IncrementCount()并在等待时阻塞,则其他线程都无法获取该锁以释放它。

Question : Do I need a different synchronization primitive to achieve my goal? 问题 :是否需要其他同步原语才能实现目标? Alternatively, am I wrong about the thread safety concern? 另外,我是否对线程安全性有误?

I think you can achieve your goal with just the standard Monitor and an additional flag. 我认为您只需使用标准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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM