Решение 11 распространенных проблем в многопоточном коде - Очереди блокировок
ОГЛАВЛЕНИЕ
Очереди блокировок
Когда интенсивность поступления на блокировку постоянно выше, чем интенсивность приобретения блокировки, может возникнуть очередь блокировки. В экстремальном случае у блокировки ждет больше служб, чем может быть обслужено, что приводит к катастрофе. Это более типично для серверных программ, где определенные блокировки, защищающие структуры данных, необходимые большинству клиентов, могут столкнуться с необычайной нагрузкой.
Например, представьте себе следующую ситуацию. В среднем каждые 100 миллисекунд прибывает 8 запросов. Для обслуживания запросов используется восемь потоков (поскольку мы работаем на 8-процессорном компьютере). Каждому из этих восьми потоков необходимо приобрести блокировку и удерживать ее в течении 20 миллисекунд, прежде чем он сможет начать осмысленную работу.
Увы, доступ к этой блокировке должен быть сериализован, так что вход в нее и выход из нее всеми восемью потоками занимает 160 секунд. После того как первый из них существует, пройдет 140 миллисекунд, прежде чем доступ к блокировке сможет получить девятый поток. Эта схема по природе своей не масштабируется, что приведет к постоянному нарастанию числа ожидающих запросов. Если со временем интенсивность прибытия запросов не снизится, то начнет истекать время ожидания клиентских запросов, что приведет к сбою.
Известно, что равноправие в блокировках способствует очередям. Причина состоит в том, что периоды времени, в течении которых блокировка могла бы быть сделана доступной, искусственно блокируются, так, чтобы потоки, прибывающие позже, дожидались, пока поток, выбранный владельцем блокировки, сможет пробудиться, переключить контекст, получить блокировку и отпустить ее. Windows с течением времени изменила все свои внутренние блокировки на неравноправные для борьбы с этой проблемой, неравноправны и мониторы CLR.
Единственным реальным решением для фундаментальной проблемы очередей является уменьшение времени удержания блокировки и организации системы таким образом, чтобы повышенная нагрузка ложилась на как можно более меньшее их число. Это проще сказать, чем сделать, но это важно для масштабируемости.
Толкучка – это ситуация, в которой много потоков пробуждается одновременно, так что все они разом требуют внимания от планировщика потоков Windows. Если, к примеру, существует 100 потоков, заблокированных на единственном событии ручного сброса, и если это событие вдруг устанавливается … что ж, результатом будет настоящий кавардак, особенно если значительной части этих потоков придется ждать снова.
Одним из способов реализации блокирующей очереди является использование события ручного сброса, которое становится неоповещенным, когда очередь пуста, и оповещенным, когда в ней что-то есть. Увы, в случае наличия большого числа ожидающих потоков во время перехода от нуля элементов к одному элементу толкучка может возникнуть все равно. Это вызвано тем, что только один поток примет единственный элемент, что делает очередь вновь пустой и по необходимости включает сброс события. При наличии 100 ожидающих потоков 99 из них пробудятся и переключат контекст (и вызовут все эти непопадания в кэш) только для того, чтобы обнаружить, что им надо ждать дальше.