В приведенном ниже фрагменте кода представлен видоизмененный рабочий цикл, выполняющий две операции с семафором.
while (TRUE) { // Рабочий цикл
WaitForSingleObject(hThrottleSem, INFINITE);
WaitForSingleObject(hMutex, INFINITE);
… Критический участок кода …
ReleaseMutex(hMutex);
ReleaseSemaphore(hThrottleSem, 1, NULL);
} // Конец рабочего цикла
Можно предложить еще одну разновидность программы. Если некоторый рабочий поток потребляет "слишком много" ресурсов, то можно заставить его выжидать некоторое время, пока значение счетчика семафора не уменьшится на несколько единиц. Однако, как уже отмечалось в предыдущей главе, использование двух последовательных циклов ожидания может стать причиной взаимоблокировки (deadlock) потоков. В следующей главе в одном из упражнений (упражнение 10.11) показано, как построить сложный объект семафора, допускающий атомарное выполнение многократных функций ожидания.
В уже упоминавшемся примере программы TimedMutualExclusion добавлен шестой параметр, являющийся начальным значением дроссельного счетчика семафора для количества активных потоков. Вы можете поэкспериментировать со значениями этого счетчика, как предлагается в одном из упражнений. На рис. 9.1 показана зависимость различных временных характеристик для шести потоков, синхронизируемых посредством одного объекта CS, от количества активных потоков, изменяющегося в интервале от 1 до 6. Во всех случаях объем выполняемой работы остается одним и тем же, но истекшее время резко увеличивается, когда количество активных потоков превышает 4.
Рис. 9.1.Зависимость производительности от количества потоков
Указанные зависимости получены на устаревших, медленных системах. Для системы Windows 2000 на базе процессора Intel 586 (Pentium), характеризующейся гораздо более высоким быстродействием, соответствующие значения истекшего времени для 1–6 потоков составили (в секундах) 0.8, 0.8, 2.3, 21.2, 28.4 и 29.0, и эти результаты могут быть последовательно воспроизведены. В этом случае ухудшение производительности становилось заметным, начиная уже с 3 активных потоков. В то же время, соответствующие временные характеристики, оцененные с использованием произвольно выбранной совокупности аналогичных систем, оказались примерно постоянными, независимо от количества активных потоков. Результаты некоторых экспериментов дают основания сделать следующие выводы:
• В системе NT5 достигнут значительный прогресс по сравнению с NT4, по следовательно демонстрирующей результаты, аналогичные тем, которые представлены на рис. 9.1.
• Получаемые результаты зависят от того, в каком режиме выполняются операции — приоритетном или фоновом, то есть, находится или не находится фокус на окне приложения, а также от присутствия в системе других выполняемых задач.
• Как правило, мьютексы работают медленнее по сравнению с объектами CS, но в случае NT5 результаты остаются примерно постоянными, независимо от количества активных потоков.
• В SMP-системах наиболее предпочтительным вариантом является дросселирование семафора при значении счетчика равном 1. В этом случае мьютексы становятся ненужными. Так, в случае двухпроцессорной системы Xeon частотой 1.8 ГГц использованные времена для варианта CS при 1, 2 и 4 активных потоках составили 1.8, 33.0 и 31.9 секунды. Соответствующие времена в случае мьютекса составили 34.0, 66.5 и 65.0 секунды.
Резюме. Дросселирование семафоров может обеспечивать хорошую производительность как для приоритетных, так и для фоновых операций даже в случае систем, загруженных выполнением других-задач. Дроссели семафоров могут играть очень важную роль в случае SMP-систем, для которых количество активных потоков должно быть равным 1. В том, что касается производительности, семафоры, по-видимому, более эффективны, чем мьютексы.
Во всем предшествующем обсуждении предполагалось, что все процессоры SMP-системы доступны всем потокам, а планирование выполнения потоков и распределение процессоров между ними осуществляет ядро. По своей сути такой простой подход является вполне естественным и согласуется с природой SMP-систем. В то же время, имеется возможность назначать потокам определенные процессоры, задавая так называемое родство процессоров (processor affinity). Родство процессоров можно использовать в нескольких ситуациях.
• Процессор может быть назначен небольшой группе, состоящей из одной и более высокоприоритетных потоков.
Читать дальше