Вопрос параллелизма .NET: Можно ли передать семафор другому потоку? - PullRequest
3 голосов
/ 06 марта 2010

У меня есть несколько потоков, которые совместно используют семафор. Поток A содержит семафор (используя блокировку), а потоки B и C ожидают этого же семафора (также используя блокировку). Потоки имеют глобальные переменные и т. Д.

Есть ли в C # техника, которую я могу использовать, чтобы закрыть поток B? Я могу установить флаг в A, и поток B проверит этот флаг и завершит работу, как только он получит контроль над семафором, но я не знаю какой-либо техники, позволяющей потоку A передавать семафор потоку B (и получать его). назад при выходе из нити B) без риска захвата нити C.

Кто-нибудь есть какие-либо предложения, как решить эту проблему дизайна? Я могу переписать программу по мере необходимости, если я подхожу к этому неправильно.

[Изменить] Комментатор указал, что я использую неправильную терминологию. Комментатор правильный - я использую критическую секцию, но, учитывая, что все выполняется в одном процессе, в этом примере критические секции функционально эквивалентны более общему термину «семафор».

[Изменить] Кто-то попросил более подробную информацию, так что вот оно.

Может быть несколько потоков, выполняющих код A. Существует только один поток, выполняющий код B.

Код A:

private static Thread workerThread = null;

lock (lockObject)
{
    ... do some work ...

    if (...condition...)
    {
        if (workerThread != null)
        {
            // Kill the worker thread and continue only after it is dead.
            quitWorkerThread = true;
            // Wait for the thread to die.
            while (workerThread.IsAlive)
            {
                Thread.Sleep(50);
            }
            workerThread = null;
            quitWorkerThread = false;
        } // if (workerThread != null)
    } // if (...condition...)

    ... do some more work ...

    if (...condition...)
    {
        if (workerThread == null)
        {
            // Start the worker thread.
            workerThread = new Thread(WorkerThread);
            workerThread.Start();
        } // if (workerThread == null)
    } // if (...condition...)

    ... do even more work ...

} // lock (lockObject)

Код B:

private void WorkerThread()
{
    while (true)
    {
        if (quitWorkerThread)
        {
            return;
        }

        Thread.Sleep (2000);

        if (quitWorkerThread)
        {
            return;
        }

        lock(lockObject)
        {
            if (quitWorkerThread)
            {
                return;
            }
            ... do some work ...
        } // lock(lockObject)
    } // while (true)
} // WorkerThread

Я подозреваю, что вариант решения Аарона будет тем, что я использую. Я больше всего надеялся, что появилось несколько более элегантное решение, но я подозреваю, что, как и все остальное в этом проекте, все это грубая сила и угловые случаи: - (.

Ответы [ 4 ]

4 голосов
/ 06 марта 2010

Я вполне уверен, что нет никакого способа передать контроль над специфическим потоком, который, кажется, является тем, что вы пытаетесь сделать. Вы можете только дать, точка - планировщик Windows должен решить, какой поток будет запущен следующим.

Ситуация такова, что у вас есть три потока, A, B и C. A имеет блокировку, B и C ожидают ее, и вы хотите, чтобы гарантировать, что B выполнится следующим.

Очевидное решение - использовать более одного примитива блокировки и / или синхронизации. Вы можете комбинировать семантику lock с ManualResetEvent. Сделайте так, чтобы поток C ожидал события и критической секции, но поток B должен только ждать критической секции. При нормальных обстоятельствах вы сигнализируете об этом событии непосредственно перед снятием блокировки, которая оставляет на усмотрение ОС решение, какой поток выполнять. В особом случае вы вообще не сигнализируете о событии, оставляя выполнение потока B, пока C все еще заблокирован.

Как только B завершен, вы сигнализируете о событии, чтобы завершить C.


(непроверенный) пример:

// Main thread is Thread A
object myLock = new Object();
AutoResetEvent myEvent = new AutoResetEvent(false);
ManualResetEvent completedEvent = new ManualResetEvent(false);

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        lock (myLock)
        {
            // Do some work
        }
    }
    completedEvent.Set();
});  // Thread B

ThreadPool.QueueUserWorkItem(s =>
{
    for (int i = 0; i < 10000; i++)
    {
        myEvent.WaitOne();
        lock (myLock)
        {
            // Do some work
        }
    }
});  // Thread C

// Main loop for thread A
while (true)
{
    lock (myLock)
    {
        // Do some work
        if (SomeSpecialCondition)
            break;
        else
            myEvent.Set();
    }
}

completedEvent.WaitOne(); // Wait for B to finish processing
if (SomeSpecialCondition) // If we terminated without signaling C...
    myEvent.Set();        // Now allow thread C to clean up

Это, по сути, назначает поток A ответственным за выполнение потока C. Потоки A и B будут нормально конкурировать, но это зависит от потока A, который сигнализирует о событии для потока C.

1 голос
/ 06 марта 2010

Решение Aaronaught выглядит здравым, но я думаю, что проще было бы использовать чуть более общее состояние и только одну блокировку.

По сути, вы передаете управление между потоками, и потоки решают, пора ли им работать или нет. Если это не так, они просто PulseAll (переместить все существующие ожидающие потоки в очередь готовности) и Wait, чтобы (быть пульсирующим, а затем) снова получить блокировку. В какой-то момент решено, когда ThreadC подходит.

public class ThreadsGettinCrazy {
  static readonly _sync = new object();
  static bool _threadCReady = false;

  public void ThreadA() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadB() {
    while (true) {
      lock(_sync) {
        while(/* my condition not met */) {
          Monitor.PulseAll(_sync);
          Monitor.Wait(_sync);
        }
        // do work, possibly set _threadCReady = true
        Monitor.PulseAll(_sync);
      }
      if (/* i'm done */) break;
    }
  }

  public void ThreadC() {
    lock(_sync) {
      while (!_threadCReady) {
        Monitor.PulseAll(_sync);
        Monitor.Wait(_sync);
      }
      // do work
    }
  }
}
1 голос
/ 06 марта 2010

(Отказ от ответственности: Если ваш единственный вариант использования - именно тот, решение @ Aaronaught простого использования ManualResetEvents, вероятно, самое простое.)

Отредактировано для дополнительного отказа от ответственности: Если вы хотите расширить эту концепцию, очень и очень опасайтесь тупиков.

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

    object lockObject = new object();
    int WaitCounter = 0;

    void B()
    {
        System.Threading.Interlocked.Increment(ref WaitCounter);

        try
        {
            lock (lockObject)
            {
            }
        }
        finally
        {
            System.Threading.Interlocked.Decrement(ref WaitCounter);
        }
    }

    void C()
    {
        while (true)
        {
            // always attempt to yield to other threads first
            System.Threading.Thread.Sleep(0);
            lock (lockObject)
            {
                if (WaitCounter > 0)
                    continue;

                // ...

                return;
            }
        }
    }

Куча дополнительного кода, но никто никогда не претендовал на параллелизм. :)

1 голос
/ 06 марта 2010

Взгляните на использование Monitor и Monitor.Pulse . Это не даст вам именно то, что вы хотите получить в результате для определенного потока, но позволит передавать управление между потоками и критическими секциями.

Мне не ясно, какую проблему вы пытаетесь решить. Вы также можете решить вашу проблему, используя ReaderWriterLockSlim .

Наконец, ваш сценарий звучит для меня как подходящее место для использования .NET events вместо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...