Самый простой способ сделать весь метод потокобезопасным? - PullRequest
9 голосов
/ 30 июля 2011

Кажется, что многопотоковое программирование нужно многому узнать, и все это немного пугает.

Для моих текущих потребностей я просто хочу защитить от повторного вызова метода из другого потока до его завершения, и мой вопрос:

Это адекватный (безопасный) способ сделать метод потокобезопасным?

class Foo
{
    bool doingWork;
    void DoWork()
    {
        if (doingWork)  // <- sophistocated thread-safety
            return;     // <-

        doingWork = true;

        try
        {
            [do work here]
        }
        finally
        {
            doingWork = false;
        }
    }
}

Если этого недостаточно, каков самый простой способ добиться этого?


РЕДАКТИРОВАТЬ: Подробнее о сценарии:

  • Существует только один экземпляр Foo

  • Foo.DoWork () будет вызываться из потока ThreadPool в Elapsed событие System.Timers.Timer.

  • Обычно Foo.DoWork () заканчивает эоны раньше, чем в следующий раз позвонил, но я хочу написать небольшой шанс, что он будет работать долго, и позвоните снова, прежде чем закончить.


(Я тоже недостаточно умен, чтобы быть уверенным, что этот вопрос может быть помечен как независимый от языка, поэтому я не сделал этого. Просвещенные читатели, не стесняйтесь делать это, если применимо.)

Ответы [ 4 ]

8 голосов
/ 30 июля 2011

Ваш код не является потокобезопасным.Вместо этого вы должны использовать ключевое слово lock.

В вашем текущем коде:

  if (doingWork)
        return;

  // A thread having entered the function was suspended here by the scheduler.

  doingWork = true;

Когда появится следующий поток, он также войдет в функцию.

Вот почему следует использовать конструкцию lock.Он в основном делает то же самое, что и ваш код, но без риска прерывания потока в середине:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        lock(lockObject)
        {
            [do work here]
        }
    }
}

Обратите внимание, что этот код имеет несколько другую семантику, чем ваш оригинал.Этот код заставит второй входящий поток ждать и затем делать работу.Ваш оригинальный код сделал второй поток просто прервать.Чтобы приблизиться к исходному коду, оператор C # lock не может быть использован.Основная конструкция Monitor должна использоваться напрямую:

class Foo
{
    object lockObject = new object;
    void DoWork()
    {
        if(Monitor.TryEnter(lockObject))
        {
            try
            {
                [do work here]
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    }
}
3 голосов
/ 30 июля 2011

Повторный вход не имеет ничего общего с многопоточностью.

Метод повторного входа - это метод, который может быть вызван изнутри самого себя в том же потоке.
Например,если метод вызывает событие, а клиентский код, который обрабатывает это событие, снова вызывает метод внутри обработчика события, этот метод является повторным входом.
Защита этого метода от повторного входа означает, что при вызове его извнутри себя он не будет ничего делать или генерировать исключение.

Ваш код защищен от повторного входа в один и тот же экземпляр объекта, если все находится в одном потоке.

Если [do work here] не способен запускать внешний код (например, вызывая событие или вызывая делегат или метод из чего-то еще), он, во-первых, не реентерабельный.

Ваш отредактированный вопрос указывает на то, что весь этот раздел для вас не имеет значения.
Вам, вероятно, все равно следует прочитать его.


Возможно, вы ( РЕДАКТИРОВАТЬ : есть)ищите исключительность - гарантируя, что метод не будет запускаться дважды одновременно при вызове несколькими потоками одновременно.
Ваш код не является эксклюзивным.Если два потока запускают метод одновременно, и они оба запускают оператор if сразу, они оба преодолеют if, затем оба установят флаг doingWork и оба запустят весь метод.

Для этого используйте ключевое слово lock.

2 голосов
/ 30 июля 2011

, если вы хотите easy код и не беспокоиться о производительности слишком много, это может быть так просто, как

class Foo
{
    bool doingWork;
object m_lock=new object();
    void DoWork()
    {
        lock(m_lock) // <- not sophistocated multithread protection
{
        if (doingWork)  
            return;     
         doingWork = true;
}


        try
        {
            [do work here]
        }
        finally
        {
lock(m_lock) //<- not sophistocated multithread protection
{
            doingWork = false;
}
        }
    }

}

Если вы хотите инкапсулировать блокировкунемного вы можете создать свойство, которое является поточно-ориентированным, как это:

public bool DoingWork
{
get{ lock(m_Lock){ return doingWork;}}
set{lock(m_lock){doingWork=value;}}
}

Теперь вы можете использовать его вместо поля, однако это приведет к увеличению времени, затрачиваемого на блокировку, так как увеличивается число блокировок.

Или вы можете использовать подход с полным забором (из большой книги по потокам Джозеф Альбахари, онлайн-потоки )

class Foo
{
  int _answer;
  bool _complete;

  void A()
  {
    _answer = 123;
    Thread.MemoryBarrier();    // Barrier 1
    _complete = true;
    Thread.MemoryBarrier();    // Barrier 2
  }

  void B()
  {
    Thread.MemoryBarrier();    // Barrier 3
    if (_complete)
    {
      Thread.MemoryBarrier();       // Barrier 4
      Console.WriteLine (_answer);
    }
  }
}

Он утверждает, что полный забор в 2 раза быстрее, чем оператор блокировки.В некоторых случаях вы можете улучшить производительность, удалив ненужные вызовы MemoryBarrier (), но использование lock просто, более понятно и менее подвержено ошибкам.

Я полагаю, что это также можно сделать с помощью класса Interlocked, основанного на поле intWorking на основе int.

0 голосов
/ 30 июля 2011

http://msdn.microsoft.com/en-us/library/system.threading.barrier.aspx

Возможно, вы захотите изучить использование барьера, он сделает всю работу за вас. Это стандартный способ управления реентерабельным кодом. Также позволяет вам контролировать количество потоков, выполняющих эту работу одновременно (если вы разрешаете более 1).

...