Совместная работа с данными по мере их создания
В многопоточных приложениях часто встречается ситуация, когда потоки не только работают с общими данными, но и ожидают их появления (то есть поток 1 должен создать данные, прежде чем поток 2 сможет их использовать). Поскольку данные являются общими, доступ к ним необходимо синхронизировать. Также необходимо предусмотреть средства для оповещения ожидающих потоков о появлении готовых данных.
Подобная ситуация обычно называется проблемой "поставщик/потребитель". Поток пытается обратиться к данным, которых еще нет, поэтому он должен передать управление другому потоку, создающему нужные данные. Проблема решается кодом следующего вида:
- Поток 1 (потребитель) активизируется, входите синхронизированный метод, ищет данные, не находит их и переходит в состояние ожидания. Предварительно он должен снять блокировку, чтобы не мешать работе потока- поставщика.
- Поток 2 (поставщик) входит в синхронизированный метод, освобожденный потоком 1, создает данные для потока 1 и каким-то образом оповещает поток 1 о наличии данных. Затем он снимает блокировку, чтобы поток 1 смог обработать новые данные.
Примечание
Не пытайтесь решить эту проблему постоянной активизацией потока 1 с проверкой состояния условной переменной, значение которой устанавливается потоком 2. Такое решение серьезно повлияет на быстродействие вашей программы, поскольку в большинстве случаев поток 1 будет активизироваться без всяких причин; а поток 2 будет переходить в ожидание так часто, что у него не останется времени на создание данных.
Связи "поставщик/потребитель" встречаются очень часто, поэтому в библиотеках классов многопоточного программирования для таких ситуаций создаются специальные примитивы. В .NET эти примитивы называются Wait и Pulse-PulseAll и являются частью класса Monitor. Рисунок 10.8 поясняет ситуацию, которую мы собираемся запрограммировать.
В программе организуются три очереди потоков: очередь ожидания, очередь блокировки и очередь выполнения. Планировщик потоков не выделяет процессорное время потокам, находящимся в очереди ожидания. Чтобы потоку выделялось время, он должен переместиться в очередь выполнения. В результате работа приложения организуется гораздо эффективнее, чем при обычном опросе условной переменной.
Рис. 10.8. Проблема "поставщик/потребитель"