Организация межпотокового взаимодействия с использованием объектов ядра операционной системы
Просмотров: 2578
Июль 2012 года
А.А. Огинский, А.М. Набатчиков, Е.А. Бурлак. Организация межпотокового взаимодействия с использованием объектов ядра операционной системы // Вестник компьютерных и информационных технологий. – 2012. – №7 (97). – С. 48-52.
ВАК
УДК: 004.032.3:004.272.4
Ключевые слова: многопоточные приложения; межпоточное взаимодействие; объекты ядра; синхронный и асинхронный опрос устройства
"ВЕСТНИК КОМПЬЮТЕРНЫХ И ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ"
Ключевые слова: многопоточные приложения; межпоточное взаимодействие; объекты ядра; синхронный и асинхронный опрос устройства
"ВЕСТНИК КОМПЬЮТЕРНЫХ И ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ"
Рассмотрены проблемы реализации многопоточных приложений в операционной системе Windows. Показаны основные трудности разработки и пути их решения. Определены основные моменты проектирования и продемонстрированы преимущества использования потоков. Представлены некоторые специфические и трудно диагностируемые ошибки и приведены методы их устранения. Проиллюстрированы основные моменты примером решения актуальной практической задачи разработки приложения, взаимодействующего с некоторым устройством ввода данных.
Проблема разработки многопоточных приложений в целом, довольно подробно рассмотрена в литературе [1]. Однако практика показывает, что при решении типовых задач, рядовой программист зачастую не занимается напрямую деталями планирования потоков. Создание подобных приложений сопряжено с рядом нехарактерных (для «обычных» алгоритмов) проблем:
Инженер, ведущий научную деятельность, зачастую представляет собой первопроходца, разрабатывая низкоуровневые интерфейсы с различными специфическими устройствами. Как правило, у «популярных» устройств имеется более простой в использовании API (Application Programming Interface). Недостаточная квалификация оборачивается неоптимальными решениями и ошибками. Подобные разработки могут быть успешно скомпилированы и работать при определенных обстоятельствах (низкая нагрузка на систему, определенные версии операционной системы (ОС), высокая производительность тестового, находящегося в идеализированных условиях, оборудования), но приводить к ошибкам или нестабильной работе в реальных условиях.
В статье рассматривается организация взаимодействия с некоторым устройством, под которым можно понимать произвольный источник данных, обладающий необходимыми функциональными возможностями интерфейса.
Сделан упор на понятность исходного кода программ, следовательно, приведенные примеры могут быть в значительной степени оптимизированы. Язык C++ выбран как наиболее мощный инструмент. Примеры тестировались в IDE (Integrated Development Environment) Code::Blocks 10.05 с использованием компилятора GNU (GNUs Not UNIX) GCC (GNU Compiler Collection).
Большинство современных устройств, используемых в исследованиях (модули цифро-аналоговых/аналого-цифровых преобразователей, адаптеры Fibre Channel, мультиплексный канал информационного обмена (МКИО) поддерживают два режима опроса – синхронный и асинхронный.
Рассмотрим детальнее основные различия этих режимов на примере простой задачи регистрации сигнала, для чего итерацию цикла опроса разделим на три составляющие:
Времена t1, t2, и t3 соответствуют средним временам выполнения соответствующих групп операций. Согласно принятым обозначениям, на рис. 1 представлена временная диаграмма цикла синхронного режима опроса устройства.
Опрос устройства совершается в одном потоке. Таким образом, время, необходимое на выполнение одного такта получения информации, выражается как Ts=t1+t2+t3.
Временная диаграмма асинхронного режима опроса представлена на рис. 2.
При асинхронном опросе операции распределяются программистом между двумя потоками, позволяя добиться минимального времени выполнения такта получения данных. Так как для задачи регистрации данных, как правило, t2>t3, то производительность системы фактически ограничивается скоростью работы опрашиваемого устройства (временем t2).
Итерации асинхронного режима опроса устройств иллюстрируются на рис. 3.
Несмотря на то, что первый такт длится Ta0, т.е. больше, чем при синхронном опросе (рис. 3, A), все последующие такты Tan (рис. 3, B) позволяют компенсировать «стартовый» простой:
Ta0=t1+t2+t1+t3
Tan=t1+max(t2,t3)=t1+t2
Приведённые выражения для первого Ta0 и последующих Tan тактов опроса устройства демонстрируют преимущества многопоточной системы. Аппаратными возможностями реализации могут выступать:
Сейчас наиболее распространенным является первый вариант. Windows 98 при работе с многоядерными процессорами использует только одно ядро, игнорируя остальные.
На практике, использование второй (асинхронной) модели опроса приводит к получению сигнала с большим разрешением по времени, что позволяет регистрировать данные от устройства чаще.
В качестве демонстрации результатов применения разных способов опроса рассмотрим результаты взаимодействия с созданной библиотекой, имитирующей устройство, способное работать в двух режимах. В обоих случаях с выхода устройства снималась синусоида с частотой 4 Гц в течение 5 с.
Так как основные операции в данном идеализированном случае выполняются за пренебрежимо малое время δt, производительность системы искусственно уменьшена путём введения дополнительных задержек. При регистрации сигнала использовались следующие значения времён: t1=δt + 0.03 с, t2=δt + 0.07 с, t3=δt + 0.035 с. При указанных параметрах частота регистрации сигнала при синхронном опросе (fs=1/Ts≈7 Гц) согласно теореме Котельникова не позволяет восстановить сигнал без искажений. На рис. 4 представлены типовые результаты замеров при асинхронном и синхронном опросах устройства.
Аналогичные результаты были получены на ПЭВМ с различными процессорами, соответствующими описанным ранее всем типам реализации многопоточности (табл. 1).
Табл. 1. Характеристики протестированных процессоров
Как видно из спектров, полученных с помощью быстрого преобразования Фурье из снятых с устройства данных, низкая производительность системы регистрации сигнала, вызванная неоптимальной реализацией программного обеспечения, может привести к ошибкам измерений.
Разработка многопоточных приложений для Windows связана с использованием объектов ядра системы, позволяющих организовать низкоуровневое взаимодействие приложений и ресурсов, при помощи механизмов межпроцессного взаимодействия.
В зависимости от API устройства концепция интерфейса может претерпевать изменения. Распространенный вариант синхронизации потоков – объект ядра «событие». Являясь наиболее простым объектом, событие играет роль логического флага, индицируя необходимое программисту состояние потока.
Для использования событий в приложении, реализуемом на C++, необходимо использовать заголовочный файл. Доступ к событию, как и к другим объектам ядра, осуществляется через описатель (дескриптор), специфичный для конкретного процесса. Перечислим кратко основные функции работы с событием (табл. 2). Более детальное описание можно найти в [1]. В целях экономии опустим аргументы функций и тип возвращаемых значений.
Табл. 2. Основные функции работы с объектом "событие"
Любой объект ядра по окончанию его использования необходимо закрыть, вызвав функцию для описателя этого объекта - после того, как все описатели, полученные от , и подобных функций, будут закрыты, ядро удалит объект [2].
Для того, чтобы остановить выполнение потока до освобождения какого-либо объекта или истечения указанного времени ожидания, необходимо вызвать функцию. Если объектов несколько, то используется функция , которая может ожидать как освобождения всех заданных объектов, так и любого из них. Возвращаемые функциями значения позволяют определить причину завершения ожидания.
Рассмотренный асинхронный опрос устройства реализован следующим образом. Программа использует два события, состояния которых будут характеризовать процесс формирования устройством ответа на запрос данных.
В цикле опроса вызывается функция API устройства, отвечающая за асинхронный запрос. Посредством этого вызова в программный модуль устройства передаётся объект "событие" и адрес области памяти, в которую требуется записать результат опроса. После вызова функции программа останавливается при помощи до момента завершения формирования результатов. Программный модуль устройства переведёт ожидаемое событие в свободное состояние.
После получения ответа на N-й запрос программа отправляет (N+1)-й запрос, после чего сохраняет N-й набор данных, параллельно с этим идёт обработка (N+1)-го запроса в потоке, созданном программным модулем устройства, и переходит в режим ожидания. Алгоритм асинхронного опроса устройства представлен на рис. 5.
Использование в данной реализации в качестве логического флага объекта ядра, а не простой переменной, обладающей необходимой областью видимости и имеющей спецификатор [3,4], исключающий переменную из оптимизации, позволяет обойти проблемы атомарного доступа, т.е. необходимость монопольного захвата ресурса обращающимся к нему потоком. Несмотря на то, что указанную проблему можно решить при помощи семейства специальных функций, таких как , , , и др. [1], гарантирующих выполнение операций над переменной без прерывания другими потоками, остаётся проблема приостановки выполнения основного потока программы в ожидании события.
Типовым решением при использовании обычных переменных является так называемая спин-блокировка - поток непрерывно выполняет «пустой» цикл, условием выхода из которого - разрешающее значение переменной. Такой подход приводит к постоянной трате процессорного времени, необходимости учёта при проектировании уровней приоритетов потоков, работающих с сигнальной переменной.
В однопроцессорных системах использование сипн-блокировки часто приводит к необходимости регулярной (в цикле) искусственной приостановки блокируемого процесса при помощи функции (отказ от процессорного времени на период, приблизительно равный указанному) или (позволяет подключить к процессору другой поток, в Windows 98 не реализована), что позволяет выделить дополнительное время для обработки второго потока, но может отрицательно повлиять на производительность программы.
Функции типа переводят поток в состояние ожидания, исключая его из числа планируемых. Система выводит поток из ждущего состояния при освобождении ожидаемого ресурса.
Подробнее о применении и существующих преимуществах спин-блокировки можно узнать в [1].
Далее приведена реализация на языке C++ организации асинхронного опроса некоторого устройства. Из данного и последующих примеров убрана часть исходного кода, обеспечивающая настройку интерфейса устройства, которая подразумевается как и подключение необходимых файлов и квалификаторы пространств имён.
Листинг 1. Асинхронный опрос
В рассмотренном примере пользователь - программист, работающий с интерфейсом, сам указывает описатели событий, используемых для сигнализации выполнения запроса.
Работа с потоками полностью скрыта и реализована на стороне программного модуля устройства.
Введение
Проблема разработки многопоточных приложений в целом, довольно подробно рассмотрена в литературе [1]. Однако практика показывает, что при решении типовых задач, рядовой программист зачастую не занимается напрямую деталями планирования потоков. Создание подобных приложений сопряжено с рядом нехарактерных (для «обычных» алгоритмов) проблем:
- «состояние гонки» — состояние, при котором результат работы программы зависит от порядка предоставления процессорного времени потокам, такое состояние является следствием ошибки проектирования приложения;
- «проблема ABA» — проблема, вызванная тем, что переменная, значение A которой было изменено потоком на некоторое другое B, а затем обратно на исходное A, определяется другим потоком как неизменённая. Проблема обычно является результатом неудачно выбранного, для конкретной задачи, механизма синхронизации;
- прочие.
Инженер, ведущий научную деятельность, зачастую представляет собой первопроходца, разрабатывая низкоуровневые интерфейсы с различными специфическими устройствами. Как правило, у «популярных» устройств имеется более простой в использовании API (Application Programming Interface). Недостаточная квалификация оборачивается неоптимальными решениями и ошибками. Подобные разработки могут быть успешно скомпилированы и работать при определенных обстоятельствах (низкая нагрузка на систему, определенные версии операционной системы (ОС), высокая производительность тестового, находящегося в идеализированных условиях, оборудования), но приводить к ошибкам или нестабильной работе в реальных условиях.
В статье рассматривается организация взаимодействия с некоторым устройством, под которым можно понимать произвольный источник данных, обладающий необходимыми функциональными возможностями интерфейса.
Сделан упор на понятность исходного кода программ, следовательно, приведенные примеры могут быть в значительной степени оптимизированы. Язык C++ выбран как наиболее мощный инструмент. Примеры тестировались в IDE (Integrated Development Environment) Code::Blocks 10.05 с использованием компилятора GNU (GNUs Not UNIX) GCC (GNU Compiler Collection).
Режимы опроса устройства
Большинство современных устройств, используемых в исследованиях (модули цифро-аналоговых/аналого-цифровых преобразователей, адаптеры Fibre Channel, мультиплексный канал информационного обмена (МКИО) поддерживают два режима опроса – синхронный и асинхронный.
Рассмотрим детальнее основные различия этих режимов на примере простой задачи регистрации сигнала, для чего итерацию цикла опроса разделим на три составляющие:
- t1 – постановку запроса устройству;
- t2 – ожидание данных (все операции, скрытые за API аппаратуры: работа драйвера, устройства, прочее);
- t3 – обработку принятых данных в программе (сохранение, анализ, подготовка к новой итерации).
Времена t1, t2, и t3 соответствуют средним временам выполнения соответствующих групп операций. Согласно принятым обозначениям, на рис. 1 представлена временная диаграмма цикла синхронного режима опроса устройства.
Опрос устройства совершается в одном потоке. Таким образом, время, необходимое на выполнение одного такта получения информации, выражается как Ts=t1+t2+t3.
Временная диаграмма асинхронного режима опроса представлена на рис. 2.
При асинхронном опросе операции распределяются программистом между двумя потоками, позволяя добиться минимального времени выполнения такта получения данных. Так как для задачи регистрации данных, как правило, t2>t3, то производительность системы фактически ограничивается скоростью работы опрашиваемого устройства (временем t2).
Итерации асинхронного режима опроса устройств иллюстрируются на рис. 3.
Несмотря на то, что первый такт длится Ta0, т.е. больше, чем при синхронном опросе (рис. 3, A), все последующие такты Tan (рис. 3, B) позволяют компенсировать «стартовый» простой:
Ta0=t1+t2+t1+t3
Tan=t1+max(t2,t3)=t1+t2
Приведённые выражения для первого Ta0 и последующих Tan тактов опроса устройства демонстрируют преимущества многопоточной системы. Аппаратными возможностями реализации могут выступать:
- многоядерный процессор (реальное одновременное выполнение);
- технология гиперпоточности (hyper-threading, выделение двух логических ядер на одном физическом);
- переключение контекста (на одноядерном процессоре без гиперпоточности).
Сейчас наиболее распространенным является первый вариант. Windows 98 при работе с многоядерными процессорами использует только одно ядро, игнорируя остальные.
На практике, использование второй (асинхронной) модели опроса приводит к получению сигнала с большим разрешением по времени, что позволяет регистрировать данные от устройства чаще.
В качестве демонстрации результатов применения разных способов опроса рассмотрим результаты взаимодействия с созданной библиотекой, имитирующей устройство, способное работать в двух режимах. В обоих случаях с выхода устройства снималась синусоида с частотой 4 Гц в течение 5 с.
Так как основные операции в данном идеализированном случае выполняются за пренебрежимо малое время δt, производительность системы искусственно уменьшена путём введения дополнительных задержек. При регистрации сигнала использовались следующие значения времён: t1=δt + 0.03 с, t2=δt + 0.07 с, t3=δt + 0.035 с. При указанных параметрах частота регистрации сигнала при синхронном опросе (fs=1/Ts≈7 Гц) согласно теореме Котельникова не позволяет восстановить сигнал без искажений. На рис. 4 представлены типовые результаты замеров при асинхронном и синхронном опросах устройства.
Аналогичные результаты были получены на ПЭВМ с различными процессорами, соответствующими описанным ранее всем типам реализации многопоточности (табл. 1).
Табл. 1. Характеристики протестированных процессоров
Производитель | Модель | Тактовая частота, МГц | Число ядер | Гиперпоточность |
Intel | Core 2 Quad Q9550 | 2833 | 4 | - |
Intel | Core i7-2600K | 3400 | 4 | + |
AMD | Phenom II X6 1090T | 3200 | 6 | - |
Intel | Core i7-930 | 2800 | 4 | + |
AMD | Athlon II X2 215 | 2700 | 2 | - |
Intel | Pentium P6000 | 1860 | 2 | - |
Intel | Celeron | 1700 | 1 | - |
Intel | Core 2 Duo E6700 | 2660 | 2 | - |
Intel | Atom N280 | 1660 | 1 | + |
Intel | Pentium 4 631 | 3000 | 1 | + |
Как видно из спектров, полученных с помощью быстрого преобразования Фурье из снятых с устройства данных, низкая производительность системы регистрации сигнала, вызванная неоптимальной реализацией программного обеспечения, может привести к ошибкам измерений.
Реализация опроса
Разработка многопоточных приложений для Windows связана с использованием объектов ядра системы, позволяющих организовать низкоуровневое взаимодействие приложений и ресурсов, при помощи механизмов межпроцессного взаимодействия.
В зависимости от API устройства концепция интерфейса может претерпевать изменения. Распространенный вариант синхронизации потоков – объект ядра «событие». Являясь наиболее простым объектом, событие играет роль логического флага, индицируя необходимое программисту состояние потока.
Для использования событий в приложении, реализуемом на C++, необходимо использовать заголовочный файл
Windows.h
Табл. 2. Основные функции работы с объектом "событие"
Функция | Описание |
CreateEvent | Создание события |
OpenEvent | Получение специфичного описателя события, созданного ранее в другом процессе (один из четырёх способов) |
SetEvent | Перевод события в свободное состояние |
ResetEvent | Перевод события в занятое состояние |
Любой объект ядра по окончанию его использования необходимо закрыть, вызвав функцию
CloseHandle
CreateEvent
OpenEvent
Для того, чтобы остановить выполнение потока до освобождения какого-либо объекта или истечения указанного времени ожидания, необходимо вызвать функцию
WaitForSingleObject
WaitForMultipleObjects
Рассмотренный асинхронный опрос устройства реализован следующим образом. Программа использует два события, состояния которых будут характеризовать процесс формирования устройством ответа на запрос данных.
В цикле опроса вызывается функция API устройства, отвечающая за асинхронный запрос. Посредством этого вызова в программный модуль устройства передаётся объект "событие" и адрес области памяти, в которую требуется записать результат опроса. После вызова функции программа останавливается при помощи
WaitForSingleObject
После получения ответа на N-й запрос программа отправляет (N+1)-й запрос, после чего сохраняет N-й набор данных, параллельно с этим идёт обработка (N+1)-го запроса в потоке, созданном программным модулем устройства, и переходит в режим ожидания. Алгоритм асинхронного опроса устройства представлен на рис. 5.
Использование в данной реализации в качестве логического флага объекта ядра, а не простой переменной, обладающей необходимой областью видимости и имеющей спецификатор
volatile
InterlockedExchangeAdd
InterlockedExchange
InterlockedExchangePointer
InterlockedCompareExchangePointer
Типовым решением при использовании обычных переменных является так называемая спин-блокировка - поток непрерывно выполняет «пустой» цикл, условием выхода из которого - разрешающее значение переменной. Такой подход приводит к постоянной трате процессорного времени, необходимости учёта при проектировании уровней приоритетов потоков, работающих с сигнальной переменной.
В однопроцессорных системах использование сипн-блокировки часто приводит к необходимости регулярной (в цикле) искусственной приостановки блокируемого процесса при помощи функции
Sleep
SwitchToThread
Функции типа
WaitForSingleObject
Подробнее о применении и существующих преимуществах спин-блокировки можно узнать в [1].
Далее приведена реализация на языке C++ организации асинхронного опроса некоторого устройства. Из данного и последующих примеров убрана часть исходного кода, обеспечивающая настройку интерфейса устройства, которая подразумевается как и подключение необходимых файлов и квалификаторы пространств имён.
Листинг 1. Асинхронный опрос
123456789101112131415161718 | DevMessage YDATA[2];/*Две структуры, используемые для хранения информации о запросе данных (определены в библиотеке устройства). Каждая содержит буфер для записи ответа устройства и объект событие, характеризующее ход выполнения запроса*/ for(short i=0;i<2;i++){ /*Создание двух событий с автосбросом (auto-reset events) для индикации выполнения запроса*/ YDATA[i].event=CreateEvent(NULL,FALSE,FALSE,NULL); } bool indx=0; /*индекс последнего отправленного устройству запроса*/ Dev_Get(&YDATA[indx]);/*постановка запроса устройству*/ while( /*условие выполнения цикла*/ ){ WaitForSingleObject(YDATA[indx].event,INFINITE); /*ожидание данных последнего запроса*/ indx=1-indx;/*смена индекса*/ Dev_Get(&YDATA[indx]); /*постановка очередного запроса устройству*/ cout<<YDATA[1-indx].V[0]<<endl;/*некоторый анализ полученных ранее данных*/ } /*Закрытие описателей ненужных событий*/ for(short i=0;i<2;i++) CloseHandle(YDATA[i].event); |
В рассмотренном примере пользователь - программист, работающий с интерфейсом, сам указывает описатели событий, используемых для сигнализации выполнения запроса.
Работа с потоками полностью скрыта и реализована на стороне программного модуля устройства.
Библиографический список
- Рихтер Дж. Windows для профессионалов: создание эффективных Win32 приложений с учетом специфики 64-разрядной версии Windows/Пер. c англ – 4-е изд. – Питер, Русская Редакция. 2001. – 752 с.
- Kernel Objects // Microsoft Windows System Information, [Электронный ресурс], 2011, URL:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms724485(v=vs.85).aspx (дата обращения: 24.04.2012)
- Страуструп Б. Язык программирования С++. – М.: Бином, 2011. – 1136 с.
- Уэллин С, Как не надо программировать на C++, Изд.: Питер, 2004 г. – 240 с.
Комментарии
http://pbs.twimg.com/media/B4AFeZvIgAABsqn.jpg