Модераторы: Partizan, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Синхронизация потоков по .NETовски 
:(
    Опции темы
AntonSaburov
Дата 1.2.2005, 18:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Штурман
****


Профиль
Группа: Модератор
Сообщений: 5658
Регистрация: 2.7.2002
Где: Санкт-Петербург

Репутация: 6
Всего: 118



Синхронизация потоков по .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. б.  Потребитель просыпается и спокойно потребляет пиво.
PM MAIL WWW ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
mr.DUDA
THandle

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »


 




[ Время генерации скрипта: 0.0813 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.