Системное программирование в среде Windows
Шрифт:
• Состязательность за очередность выполнения будет существовать даже при использовании мьютексов для защиты разделяемых данных. Наличие защиты данных само по себе не может служить гарантией определенной очередности получения доступа к разделяемым данным различными потоками. Например, если один поток добавляет средства на банковский счет, а другой снимает их со счета, то одна только защита счета с помощью мьютекса не сможет гарантировать, что внесение денег на счет всегда будет осуществляться раньше, чем их снятие. В упражнении 10.14 показано, как организовать управление очередностью выполнения потоков.
• Необходимость
• Не допускайте разделения событий предикатами. В реализациях, использующих переменные условий, каждое событие должно связываться с отдельным предикатом. Кроме того, событие всегда должно использоваться с одним и тем же мьютексом.
• Остерегайтесь совместного использования стеков и связанного с этим риска порчи памяти. Никогда не забывайте о том, что при возврате из функции или завершении выполнения потока их локальные области памяти становятся недействительными. Память в области стека потока может использоваться другими потоками, но вы должны быть уверены в том, что первый поток все еще существует.
• Следите за использованием модификатором класса памяти volatile в необходимых случаях. Всякий раз, когда возможно изменение разделяемой переменной в одном потоке и обращение к нему в другом, класс памяти, используемой этой переменной, должен быть определен как volatile, что будет гарантировать использование ячеек памяти при сохранении и извлечении этой переменной потоками, а не регистров, специфичных для каждого потока.
Ниже приводятся некоторые дополнительные рекомендации и мнемонические правила, которые вам могут пригодиться.
• Правильно пользуйтесь моделью переменных условий, проверяя, чтобы два разных мьютекса не использовались с одним и тем же событием. Хорошо изучите модель переменных условий, на которой основана ваша программа. Прежде чем вызывать функцию ожидания перехода переменной условия в сигнальное состояние, убедитесь в выполнении условия, выраженного предикатом.
• Старайтесь вникнуть в смысл используемых инвариантов и предикатов переменных условий, даже если они имеют неформальное выражение. Убедитесь в том, что условие, выраженное предикатом, всегда выполняется за пределами критического участка кода.
• Стремитесь к упрощению. Многопоточное программирование уже само по себе является достаточно сложным, чтобы еще дополнительно усложнять его введением трудно воспринимаемых моделей и логики. Если ваша программа становится чрезмерно сложной, постарайтесь оценить, продиктована ли эта сложность действительной необходимостью или же является следствием того, что программа была неудачно спроектирована.
• Тестируйте программы как на однопроцессорных, так и на многопроцессорных системах, а также на системах с различными тактовыми частотами и другими характеристиками. Природа некоторых дефектов такова, что на однопроцессорных системах они проявляются лишь в редких случаях или вообще никогда, в то время как на SMP-системах вы их сразу определите, и наоборот. Аналогичным образом, чем шире круг характеристик систем, на которых будет выполняться программа, содержащая дефекты, тем выше вероятность сбоя.
• Тестирование является необходимой, но не достаточной
• Смиритесь! Как бы вы ни старались следовать этим советам, ошибок в своих программах вам не избежать. Это утверждение справедливо даже в случае однопоточных программ, не говоря уже о многопоточных, которые предоставляют нам гораздо больше разнообразных и интереснейших возможностей создавать себе проблемы.
За рамками Windows API
В своем рассмотрении мы намеренно ограничились случаем Windows API. Вместе с тем, Microsoft предоставляет дополнительные средства доступа к таким объектам ядра, как потоки. Так, класс ThreadPool, доступный в C++, С# и других языках программирования, позволяет создавать пулы потоков и очереди задач потоков (для этого служит метод QueueUserWorkItem класса ThreadPool).
Кроме того, Microsoft реализует службу Microsoft Message Queuing (MSMQ), которая предоставляет услуги по передаче сообщений между сетевыми системами. Приведенный в данной главе пример должен был продемонстрировать вам, насколько полезными могут быть универсальные системы очередизации сообщений. MSMQ документирована на Web-сайте компании Microsoft.
Резюме
Разработка многопоточных программ значительно упрощается, если используются хорошо себя зарекомендовавшие, известные модели и методы программирования. В этой главе была продемонстрирована полезность модели переменных условий и решен ряд сравнительно сложных, но важных проблем программирования. АРС позволяют одному потоку посылать сигналы другому и вызывать в нем выполнение определенных действий, что позволяет отменять выполнение потоков таким образом, чтобы все потоки в системе имели возможность корректно завершиться.
Сложность синхронизации и управления потоками объясняется тем, что существует множество путей решения одной и той же задачи, и при выборе метода следует учитывать его сложность и характер влияния, которое он может оказать на производительность. Чтобы проиллюстрировать все многообразие доступных возможностей, пример трехступенчатого конвейера был реализован несколькими отличными друг от друга способами.
Тщательное продумывание проекта и путей его реализации является предпочтительным способом улучшения качества программы. Возложение чрезмерных надежд на результаты тестирования и отладку без того, чтобы уделить должное внимания деталям, может привести к возникновению серьезных проблем, обнаружить и устранить которые будет очень трудно.
В следующих главах
В главе 11 показано, как организовать взаимодействие между процессами и потоками, выполняющимися внутри этих процессов, используя именованные каналы (named pipes) и почтовые ящики (mailslots) Windows. В качестве основного примера выбрана клиент-серверная система, в которой для обслуживания запросов клиентов используется пул рабочих потоков. В главе 12 эта же система реализуется с привлечением гнезд Windows Sockets, что делает ее пригодной для использования стандартных протоколов. В обновленной клиент-серверной системе применяются безопасные библиотеки DLL с многопоточной поддержкой, а сервер использует внутрипроцессный сервер (in-process server) на основе DLL.