Безопасен ли этот поток использования lock + ManualResetEvent? - PullRequest
0 голосов
/ 13 июня 2019

Это отдельный вопрос, основанный на этом вопросе . Напомним, что у меня есть две функции, которые управляют счетчиком, и функция 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 () и передает ожидание события () - начальное состояние ManualResetEvent уже сигнализируется.
  2. Затем поток B запускается и выполняет все функции OverwriteCount ().
  3. Поток A затем продолжает, получая блокировку и увеличивая _myCount.

Это нарушает мою цель, поскольку _myCount изменится после вызова OverwriteCount () до запуска OnTimer.

Отклоненная альтернатива : Я могу переместить mre.Wait () в пределах блокировки (_sync), но это может привести к тупиковой ситуации. Если поток A вызывает IncrementCount () и блокирует ожидание, никакие другие потоки не могут получить блокировку для ее снятия.

Вопрос : Нужен ли другой примитив синхронизации для достижения моей цели? Или я ошибаюсь из-за проблем безопасности потоков?

1 Ответ

1 голос
/ 13 июня 2019

Я думаю, что вы можете достичь своей цели только с помощью стандартного 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...