Требуется обновление браузера.

Организация межпотокового взаимодействия с использованием объектов ядра операционной системы


Просмотров: 2926
Июль 2012 года
А.А. Огинский, А.М. Набатчиков, Е.А. Бурлак. Организация межпотокового взаимодействия с использованием объектов ядра операционной системы // Вестник компьютерных и информационных технологий. – 2012. – №7 (97). – С. 48-52.
ВАК
УДК: 004.032.3:004.272.4
Ключевые слова: многопоточные приложения; межпоточное взаимодействие; объекты ядра; синхронный и асинхронный опрос устройства

"ВЕСТНИК КОМПЬЮТЕРНЫХ И ИНФОРМАЦИОННЫХ ТЕХНОЛОГИЙ"
Рассмотрены проблемы реализации многопоточных приложений в операционной системе Windows. Показаны основные трудности разработки и пути их решения. Определены основные моменты проектирования и продемонстрированы преимущества использования потоков. Представлены некоторые специфические и трудно диагностируемые ошибки и приведены методы их устранения. Проиллюстрированы основные моменты примером решения актуальной практической задачи разработки приложения, взаимодействующего с некоторым устройством ввода данных.

Введение


Проблема разработки многопоточных приложений в целом, довольно подробно рассмотрена в литературе [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 представлена временная диаграмма цикла синхронного режима опроса устройства.
01.png
Рисунок 1. Синхронный режим опроса устройства

Опрос устройства совершается в одном потоке. Таким образом, время, необходимое на выполнение одного такта получения информации, выражается как Ts=t1+t2+t3.

Временная диаграмма асинхронного режима опроса представлена на рис. 2.
02.png
Рисунок 2. Асинхронный режим опроса устройства

При асинхронном опросе операции распределяются программистом между двумя потоками, позволяя добиться минимального времени выполнения такта получения данных. Так как для задачи регистрации данных, как правило, t2>t3, то производительность системы фактически ограничивается скоростью работы опрашиваемого устройства (временем t2).

Итерации асинхронного режима опроса устройств иллюстрируются на рис. 3.
03.png
Рисунок 3. Итерации асинхронного режима опроса устройства: A - первый такт; B - последующие такты

Несмотря на то, что первый такт длится 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 представлены типовые результаты замеров при асинхронном и синхронном опросах устройства.
04.png
Рисунок 4. Результаты асинхронного и синхронного опроса устройства

Аналогичные результаты были получены на ПЭВМ с различными процессорами, соответствующими описанным ранее всем типам реализации многопоточности (табл. 1).

Табл. 1. Характеристики протестированных процессоров
ПроизводительМодельТактовая частота, МГцЧисло ядерГиперпоточность
IntelCore 2 Quad Q955028334-
IntelCore i7-2600K34004+
AMDPhenom II X6 1090T32006-
IntelCore i7-93028004+
AMDAthlon II X2 21527002-
IntelPentium P600018602-
IntelCeleron17001-
IntelCore 2 Duo E670026602-
IntelAtom N28016601+
IntelPentium 4 63130001+

Как видно из спектров, полученных с помощью быстрого преобразования Фурье из снятых с устройства данных, низкая производительность системы регистрации сигнала, вызванная неоптимальной реализацией программного обеспечения, может привести к ошибкам измерений.

Реализация опроса


Разработка многопоточных приложений для Windows связана с использованием объектов ядра системы, позволяющих организовать низкоуровневое взаимодействие приложений и ресурсов, при помощи механизмов межпроцессного взаимодействия.

В зависимости от API устройства концепция интерфейса может претерпевать изменения. Распространенный вариант синхронизации потоков – объект ядра «событие». Являясь наиболее простым объектом, событие играет роль логического флага, индицируя необходимое программисту состояние потока.

Для использования событий в приложении, реализуемом на C++, необходимо использовать заголовочный файл
Windows.h
. Доступ к событию, как и к другим объектам ядра, осуществляется через описатель (дескриптор), специфичный для конкретного процесса. Перечислим кратко основные функции работы с событием (табл. 2). Более детальное описание можно найти в [1]. В целях экономии опустим аргументы функций и тип возвращаемых значений.

Табл. 2. Основные функции работы с объектом "событие"
ФункцияОписание

CreateEvent
Создание события

OpenEvent
Получение специфичного описателя события, созданного ранее в другом процессе (один из четырёх способов)

SetEvent
Перевод события в свободное состояние
ResetEvent
Перевод события в занятое состояние

Любой объект ядра по окончанию его использования необходимо закрыть, вызвав функцию 
CloseHandle
для описателя этого объекта - после того, как все описатели, полученные от 
CreateEvent
, 
OpenEvent
и подобных функций, будут закрыты, ядро удалит объект [2].

Для того, чтобы остановить выполнение потока до освобождения какого-либо объекта или истечения указанного времени ожидания, необходимо вызвать функцию
WaitForSingleObject
. Если объектов несколько, то используется функция
WaitForMultipleObjects
, которая может ожидать как освобождения всех заданных объектов, так и любого из них. Возвращаемые функциями значения позволяют определить причину завершения ожидания.

Рассмотренный асинхронный опрос устройства реализован следующим образом. Программа использует два события, состояния которых будут характеризовать процесс формирования устройством ответа на запрос данных.

В цикле опроса вызывается функция API устройства, отвечающая за асинхронный запрос. Посредством этого вызова в программный модуль устройства передаётся объект "событие" и адрес области памяти, в которую требуется записать результат опроса. После вызова функции программа останавливается при помощи
WaitForSingleObject
до момента завершения формирования результатов. Программный модуль устройства переведёт ожидаемое событие в свободное состояние.

После получения ответа на N-й запрос программа отправляет (N+1)-й запрос, после чего сохраняет N-й набор данных, параллельно с этим идёт обработка (N+1)-го запроса в потоке, созданном программным модулем устройства, и переходит в режим ожидания. Алгоритм асинхронного опроса устройства представлен на рис. 5.
05.png
Рисунок 5. Алгоритм асинхронного опроса устройства

Использование в данной реализации в качестве логического флага объекта ядра, а не простой переменной, обладающей необходимой областью видимости и имеющей спецификатор
volatile
[3,4], исключающий переменную из оптимизации, позволяет обойти проблемы атомарного доступа, т.е. необходимость монопольного захвата ресурса обращающимся к нему потоком. Несмотря на то, что указанную проблему можно решить при помощи семейства специальных функций, таких как
InterlockedExchangeAdd
,
InterlockedExchange
,
InterlockedExchangePointer
,
InterlockedCompareExchangePointer
и др. [1], гарантирующих выполнение операций над переменной без прерывания другими потоками, остаётся проблема приостановки выполнения основного потока программы в ожидании события.

Типовым решением при использовании обычных переменных является так называемая спин-блокировка - поток непрерывно выполняет «пустой» цикл, условием выхода из которого - разрешающее значение переменной. Такой подход приводит к постоянной трате процессорного времени, необходимости учёта при проектировании уровней приоритетов потоков, работающих с сигнальной переменной.

В однопроцессорных системах использование сипн-блокировки часто приводит к необходимости регулярной (в цикле) искусственной приостановки блокируемого процесса при помощи функции
Sleep
(отказ от процессорного времени на период, приблизительно равный указанному) или
SwitchToThread
(позволяет подключить к процессору другой поток, в Windows 98 не реализована), что позволяет выделить дополнительное время для обработки второго потока, но может отрицательно повлиять на производительность программы.

Функции типа
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 с.

Внешние источники:

  1. Kernel Objects // Microsoft Windows System Information

Комментарии

Инкогнито
  Загружаем captcha
Stanislav Zheronkin
2 Stanislav Zheronkin  Фейсбук 03 февраля 2015 16:09
Очень интересное исследование.
Дмитрий Маслов
1 Дмитрий Маслов  ВКонтакте 28 января 2015 18:36
Многопоточность, ожидания и реальность:
http://pbs.twimg.com/media/B4AFeZvIgAABsqn.jpg