Повторим то же самое, но уже для случая параллельных процессов:
# nice -n-19 p4-1 2 10
Rescheduling interval = 0.036876 msec.
Forks scheduling time: 1622.87 msec [867633362 cycles]
# nice -n-19 p4-1 20 10
Rescheduling interval = 0.036876 msec.
Forks scheduling time: 16682.1 msec [8918698991 cycles]
# nice -n-19 p4-1 200 10
Rescheduling interval = 0.036876 msec
Forks scheduling time: 173398 msec. [92703484992 cycles]
Здесь наблюдается лишь незначительное увеличение крутизны линейной зависимости, что можно отнести к некоторым накладным расходам на поддержание достаточно большого числа записей о процессах в таблицах менеджера процессов, но величина этого эффекта также весьма малосущественна.
В итоге, в отношении «легковесности» потоков можно сказать следующее:
• При необходимости динамическогосоздания параллельных ветвей в ходе выполнения программы (а это достаточно классический случай, например в разнообразных сетевых серверах, создающих ветвь обслуживания для каждого нового клиента) производительность приложения, функционирующего на основе потоков, может быть значительно выше (до нескольких порядков), а время реакции соответственно ниже.
• При статическомвыполнении (фиксированном количестве параллельных ветвей в приложении) эффективность приложений, построенных на параллельных потоках или параллельных процессах, практически не отличается. Более того, эффективности таких приложений не отличаются и от классической последовательной организации приложения, работающего в одном потоке.
• Существует дополнительный фактор, обеспечивающий «легковесность» потоков в противовес процессам, — это легкость и эффективность их взаимодействия в едином адресном пространстве. В случае процессов для обеспечения таких взаимодействий возникает необходимость привлечения «тяжеловесных» механизмов IPC разнообразной природы (именованные и неименованные каналы, разделяемая память, обмен UNIX-сообщениями и другие). При рассмотрении обмена сообщениями QNX мы еще раз убедимся в том, что обмены и взаимодействия между процессами могут требовать весьма существенных процессорных ресурсов, а при обменах с интенсивным трафиком могут стать доминирующей компонентой, определяющей пределы реальной производительности системы.
Пример: синхронное выполнение кода
Выше приводилось достаточно много подобных примеров, но это были примеры, так сказать, «локальные», фрагментарные, иллюстрирующие использование какой-то одной возможности применительно к потокам. Сейчас мы приведем пример, реализующий часто возникающую на практике возможность. Некоторые программные действия (функции) мы хотели бы запускать периодически с фиксированным временным интервалом T, что весьма напоминает действия и аппаратной реализации, которые должны быть выполнены по каждому импульсу «синхронизирующей последовательности».
Простейшая реализация могла бы выглядеть так:
...
while(true) {
delay(T);
func();
}
Но это очень «слабое» решение:
• Задержка, обеспечиваемая функцией пассивной задержки delay()
, согласно требованиям POSIX не может быть меньшеуказанного параметра T, но... может быть сколь угодно больше! (В [4] мы писали, что при T = 1 реальная величина задержки будет составлять не 1 мсек., как можно было бы ожидать, а с большой степенью вероятности 3 мсек., и там же мы подробно показывали, как это происходит.)
• Если в системе одновременно с этим приложением работает процесс (поток) более высокого приоритета, то наше приложение может вообще никогда «не проснуться», по крайней мере, пока это не «соизволит» санкционировать параллельное приложение.
• Здесь мы обеспечиваем только одну синхронизированную последовательность вызовов функции func()
. А если бы нам потребовалось несколько (много) синхросерий, в каждой из которых выполняется своя функция, а периоды серий не кратны друг другу?
• Наконец, время выполнения целевой функции func()
включается в период одного «кругового пробега» цикла, то есть период T отсчитывается от концапредыдущего выполнения функции до началатекущего, а это не совсем то, что мы подразумевали при использовании термина «синхронное».
• Более того, если время выполнения функции func()
достаточно флуктуирует от одного вызова до другого (например, из-за изменений данных, с которыми работает функция), то периоды вызовов начинают «гулять», а дисперсия периода результирующей последовательности вызовов func()
становится просто непомерно большой.
Читать дальше
Конец ознакомительного отрывка
Купить книгу