2010-03-06 3 views
3

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

Есть ли способ в 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 

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

+2

«Резьба A содержит семафор (с использованием блокировки)» - инструкция 'lock' является критическим разделом. Используете ли вы семафоры или критические разделы (блокировку)? – Aaronaught

+0

Если вы используете семафоры, вы, вероятно, будете использовать waitOne(). – Zach

+2

FYI, критический раздел сильно отличается от семафора даже в рамках одного процесса. Критический раздел позволяет точно * одному * потоку удерживать ресурс; семафор может допускать несколько. Семантика блокировки также различна; поток может повторно вводить свой критический раздел сколько угодно раз, но семафоры не заботятся о идентичности потоков, поэтому повторное получение его слишком много раз будет заторможено. Извините за педантизм, но различие очень важно. – Aaronaught

ответ

4

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

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

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

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


An (непроверенный) пример будет:

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

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

+0

Быстрое примечание на стороне - другие люди предложили использовать переменные 'bool' или' int' для состояния, что является допустимым подходом; одна из причин, по которой я использовал дополнительный примитив sync, заключается в том, что он может пересекать границы экземпляров, тогда как проверка 'bool' или использование' Interlocked' применима только тогда, когда все методы потока находятся в одном классе.Кроме того, не отбрасывайте событие 'workCompleted', это не является абсолютно необходимым, это просто поможет с очисткой в ​​конце, если вам нужно убедиться, что C запускает * после * B на заключительных этапах. – Aaronaught

+0

Благодаря товарищу Torontonian за ваш (невероятно) быстрый и информативный ответ. Это очень ценится. Неудивительно, что у вас есть> 13 тысяч очков! –

1

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

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

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

+0

Я действительно не понимаю, как здесь применяются функции «ReaderWriterLockSlim» или делегаты событий, а не конкретные? – Aaronaught

+0

Все зависит от решаемой проблемы. Я думал, что если проблема связана с синхронизированным доступом к некоторым общим данным, возможно, ReaderWriterLock решит проблему вместо того, чтобы использовать и освобождать блокировки, мониторы и т. Д. Если фактическая проблема заключалась в желании оповещать различные объекты, когда что-то происходит , тогда события звучат как путь. Поскольку первоначальный вопрос, казалось, не содержал описания проблемы, которую я решил, я пытался охватить все базы. – Firestrand

+0

Большое спасибо за указатель на Monitor.Pulse. Это была специальная конструкция параллелизма .NET, с которой я не знаком. –

1

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

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

Если есть ситуации, в которых вы могли бы хотеть 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; 
      } 
     } 
    } 

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

+0

Разве вы не используете 'Interlocked.Read', когда вы берете значение' WaitCounter' в 'C'? – Aaronaught

+0

@Aaronaught: Я не думаю, что вам нужно для Int32, но я не уверен на 100% ... на самом деле это был бы хороший новый вопрос. : p – Tanzelax

+0

@Aaronaught: ... и это уже было задано на самом деле. http://stackoverflow.com/questions/1186515/interlocked-and-volatile Похоже, что это нормально без Interlocked.Read. – Tanzelax

1

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

В основном вы передаете управление между потоками и нитями, решая, пора ли им работать или нет. Если это не так, они просто PulseAll (перемещают все существующие ожидающие потоки в очередь ожидания) и Wait to (пульсируют, а затем) снова получают блокировку. В какой-то момент решено, когда 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 
    } 
    } 
} 
+0

Большое спасибо за код, демонстрирующий использование Monitor.Pulse. Я удивлен и благодарен за ваши усилия и с радостью осматриваю все три подхода. Независимо от того, какой я использую, все они, безусловно, будут образовательными. –

+0

Добро пожаловать! –

Смежные вопросы