Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > помогите разобраться с Queue и lock


Автор: Гость_ser 1.2.2005, 05:26
два потока и стек типа FIFO: один запиcывает в буфер каждую ms. простой обект типа int, другой считывает из него. вроде Queue как нельзя лучше подходит для этого случая.
Код
public static Queue qBuffer = new Queue();
public static Queue qsyncBuf;
1-й поток:
Код
lock( qBuffer.SyncRoot ) {
   qBuffer.Enqueue( i );  
   qsyncBuf = Queue.Synchronized( qBuffer );      
}
2-й поток:
Код
int iDequeue;
...
lock( qBuffer.SyncRoot ) {

   qsyncBuf = Queue.Synchronized( qBuffer );

   if ( qsyncBuf.Count != 0 )
       iDequeue = (int)qsyncBuf.Dequeue();    
   else
       continue;      
}
из-за блокировки во втором потоке около половины значений не записываются в Queue в первом.

Автор: Domestic Cat 1.2.2005, 06:01
Я не совсем понял чего ты хочешь сделать, подробнее плиз.

Автор: Guest 1.2.2005, 06:18
в первом потоке я запиcываю в буфер Queue значение типа int.
во втором потоке считываю из этого буфера значение стоящее в очереди первым (FIFO).
как я понимаю после чтения посредством Dequeue это значение из очереди удаляется, что мне и надо.


Автор: Domestic Cat 1.2.2005, 06:54
Тогда сразу для ФАКа - будьмо надеяться что добрые боги этого форума сжалятся :}

-----------------------------------------------------------------------------------------------------------

Синхронизация потоков по .NETовски

Есть один стандартный пример о синхронизации потоков, используемый во многих книжках - задача о производителе и потребителе (Producer и Consumer). Первый чего-нибудь производит, второй потребляет. Поскольку оба по жизни люди независимые, то и описываются они двумя различными тредами.

Основная проблема в том, что оба имеют дело с одним и тем же объектом - продуктом. Ели мы запустим 2 треда, н заботясь о синхронизации, то столкнемся с ситуацией когда потребитель попытается пользоваться товаром, который еще не произведен (в реале он повернется и уйдет, а у нас будет брошен нехороший иксепшн); или находится в процессе производства ( такое вообще представить трудно в жизни, в программе же баг такой можно долго ловить).

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

В .NET / Java дело происходит точно так же. Рассмотрим пример с производителем пива, и потребителем, который не хочет пить менее 10 ящиков пива за раз и требует чтобы пиво было подано в том порядке, в каком оно было произведено.

Как решить проблему?
Естественно, нужно использовать Queue, хранящую ящики пива. Вопрос в том, как избавиться от двух нехороших вещей - потребителя, получающего пустую Queue или неполную Queue?

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

Код

namespace Locks
{
class MainClass
{
 private Queue beer;
 private bool isBeerReady;

 public MainClass()
 {
  Console.WriteLine("************Start************");
  beer = new Queue(15);
  Thread producer = new Thread(new ThreadStart(Producer));
  Thread consumer = new Thread(new ThreadStart(Consumer));
  Random random = new Random();
  if (random.NextDouble() < 0.5)
  {
   producer.Start();
   consumer.Start();
   Console.WriteLine("Produсer makes, consumer waits");
  }
  else
  {
   consumer.Start();
   producer.Start();
   Console.WriteLine("Consumer waits, produсer makes");
  }
  Console.WriteLine("*************End*************");
 }

 [STAThread]
 static void Main(string[] args)
 {
  new MainClass();
 }

 public void Producer()
 {
  lock(beer.SyncRoot)
  {
   for (int i = 0; i < 10; i++)
   {
    Thread.Sleep(5);
    beer.Enqueue(i);
   }
   isBeerReady = true;
   Monitor.PulseAll(beer.SyncRoot);
   Console.WriteLine("Producer produced " + beer.Count + " packs of beer");
  }
 }

 public void Consumer()
 {
  lock(beer.SyncRoot)
  {
   while (! isBeerReady)
    Monitor.Wait(beer.SyncRoot);
   for (int i = 0; i < 10; i++)
   {
    Thread.Sleep(5);
    Console.WriteLine("Consumer is drinking " + beer.Dequeue() + "th pack of beer");
   }
  }
  Console.WriteLine("Consumer is good, very good.");
 }
}
}




Ну во-первых мы считаем, что для того, чтобы поизвести / выпить ящик пива требуется 5 мс.
Далее пойдем по решению.
В конструкторе создается 2 треда. Далее эти треды запускаются в случайном порядке.

Что делает Производитель.
1. Заходит в метод Producer.
2. Пытается забрать объект SyncRoot у beer. Тут возможны 2 ситуации:

2.а. Этот объект свободен (Потребитель еще не начал выполнять свой метод). Тогда Производитель забирает лок и блокирует Потребителю вход в Блок 2.
2.б. Этот объект занят (отобран Потребителем). Производитель ждет снаружи Блока 1.

Далее продолжаем оба варианта по отдельности, начинаем с 2.а.
--3.а. Производитель начинает производить пиво. Если теперь Потребитель попытается войти в Блок 2, он вынужден будет ждать снаружи, т.к. у него нет лока SyncRoot .

4.а. Производитель производит пиво, меняет флаг isBeerReady и вызывает Мonitor.PulseAll() (оба ничего в данном случае не делают), и высвобождает лок.

5.а. Истомленный жаждой Потребитель наконец получает лок, пропускает цикл и выпивает все.

--- 3.б. А что если первым лок получил Потребитель? С тредами ничего заранее не известно, даже если бы я не использовал рандом а четко указал какой тред запускается первым, все равно неизвестно как бы легла рука планировщика и какой бы слайс времени она бы отрезала каждому из тредов.
Итак, Потребитель получает лок. Но isBeerReady == false и Потребитель попадает в бесконечный цикл. А лок-то у него, и потому Производитель так бы и остался навечно ждатЬ доступа к локу, если бы не Monitor.Wait. Этот метод заставляет Потребителя отпустить лок и остановиться.

4.б. Теперь Производитель получает доступ к Блоку 1 и производит пиво. Перед самым завершением работы он уведомляет всех ждущих на объекте SyncRoot (то есть Потребителя) что усе готово - методом Monitor.PulseAll.

5. б. Потребитель просыпается и спокойно потребляет пиво.

Автор: Guest 1.2.2005, 07:02
после того как закомментировал блокировку lock(...) во втором потоке, (все остальное оставил как есть) похоже что все работает без потерь (если не принимать во внимание CPU Usage=95% и готовность проц. блока на взлет от надрывной работы вентилятора процессора). Почему что-то работает, обяснить не могу, ибо скопировал куски из примеров майкрософта и подозреваю что перемудрил с синхронизацией на каждой итерации записи. Может есть механизм попроще?

Автор: Guest 1.2.2005, 07:04
извини, не заметил сообщения выше. сейчас почитаю, переварю

Автор: -ser- 1.2.2005, 11:01
за пример спасибо, классика. но один важный вопрос для моего конкретного случая.
Цитата
2.б. Этот объект занят (отобран Потребителем). Производитель ждет снаружи Блока 1.

у меня старая добрая соц. система с 5-ти летним планом, для выполнения которого производитель обязан варить и отгружать пиво в три смены БЕЗ ОСТАНОВКИ независимо от поставщика. перепроизводство не его забота.
не получится ли в нашем случае так, что если машина заказчика со свежеотгруженным продуктом по каким-то причинам застряла на проходной на больше чем положенное время, то у нас встанет весь конвейер, сорвем пятилетку. Боюсь, что наивысший приоритет для Поставщика и наинизший для Заказчика не помогут.

Автор: Domestic Cat 1.2.2005, 11:09
Цитата(ser @ 1.2.2005, 02:01)
не получится ли в нашем случае так, что если машина заказчика со свежеотгруженным продуктом по каким-то причинам застряла на проходной на больше чем положенное время, то у нас встанет весь конвейер, сорвем пятилетку.


Wait заставляет Потребителя выпустить лок, потому такая ситуация невозможна (по крайней мере в этом примере).
Для более сложных вариантов решается все аналогично, хотя и поднапрячься нужно smile

Автор: -ser- 1.2.2005, 11:20
насколько я понял Wait потребителя для случая когда на складе пусто, а я имел ввиду задержку в цикле for Потребителя? может я что-то не понимаю. ладно буду пробовать. во всяком случае примерчик неплохой.

Автор: Domestic Cat 1.2.2005, 11:28
Цитата(ser @ 1.2.2005, 02:20)
задержку в цикле for Потребителя?


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

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)