QNX/UNIX: Анатомия параллелизма
QNX/UNIX: Анатомия параллелизма читать книгу онлайн
Книга адресована программистам, работающим в самых разнообразных ОС UNIX. Авторы предлагают шире взглянуть на возможности параллельной организации вычислительного процесса в традиционном программировании. Особый акцент делается на потоках (threads), а именно на тех возможностях и сложностях, которые были привнесены в технику параллельных вычислений этой относительно новой парадигмой программирования. На примерах реальных кодов показываются приемы и преимущества параллельной организации вычислительного процесса. Некоторые из результатов испытаний тестовых примеров будут большим сюрпризом даже для самых бывалых программистов. Тем не менее излагаемые техники вполне доступны и начинающим программистам: для изучения материала требуется базовое знание языка программирования C/C++ и некоторое понимание «устройства» современных многозадачных ОС UNIX.
В качестве «испытательной площадки» для тестовых фрагментов выбрана ОСРВ QNX, что позволило с единой точки зрения взглянуть как на специфические механизмы микроядерной архитектуры QNX, так и на универсальные механизмы POSIX. В этом качестве книга может быть интересна и тем, кто не использует (и не планирует никогда использовать) ОС QNX: программистам в Linux, FreeBSD, NetBSD, Solaris и других традиционных ОС UNIX.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
// наконец, 0 и который определяет, что будет
// происходить дальше с вызывающим потоком...
if ((tpp = thread_pool_create(&pool_attr, POOL_FLAG_EXIT_SELF)) == NULL)
perror("create pool"), exit(EXIT_FAILURE);
thread_pool_start(tpp);
...
}
Но всю эту последовательность действий мы уже видели ранее при описании динамического пула потоков, и какого-то специфического отношения к созданию именно менеджера ресурса она не имеет.
Вот такими элементарными манипуляциями мы превращаем менеджер ресурса (практически любой менеджер!) в многопоточный. С другой стороны, простота трансформации одной формы в другую подсказывает простое и эффективное решение: вначале всегда пишите одно- поточный менеджер, поскольку в отладке и понимании он намного проще, и только потом при необходимости трансформируйте его в многопоточный.
Множественные каналы
Техника написания менеджеров ресурсов в QNX открывает перспективу для простого и ясного написания драйверов системы без необходимости «залезать» в специфические низкоуровневые детали. Тем не менее в описаниях технологии создания менеджеров ресурсов есть один аспект, который имеет непосредственное отношение к синхронизации параллельных ветвей, и нельзя сказать, что этот вопрос не освещен в технической документации, однако его составляющие детали «размазаны» по документации, и общую картину приходится восстанавливать.
Суть вопроса в следующем. Писать менеджер ресурсов как системный драйвер некоторого специфического аппаратного устройства — это удел единиц (на каждое устройство — по одному разработчику! … шутка), но менеджер ресурсов — это прекрасная альтернатива для описания чисто программных «псевдоустройств». Например, это могла бы быть некоторая оконная GUI-подсистема, в которой
open()
write()
read()
Однако для «истинных драйверов» запросы
open()
read()
write()
Гораздо свободнее может себя чувствовать разработчик драйвера псевдоустройства (программной модели): здесь каждый запрос
open()
read()
write()
Это настолько часто используемая модель, что она заслуживает отдельного рассмотрения. Дополнительную сложность создает то обстоятельство, что мы, как уже отмечалось, договорились писать программный код на С++, а здесь нам предстоит переопределять из своего кода определения в заголовочных файлах менеджера ресурсов, не нарушая их C-синтаксис.
Ниже показан текст простейшего многопоточного менеджера (исключены даже самые необходимые проверки), ретранслирующего по нескольким каналам независимо получаемые текстовые строки (строки кода, принципиальные для обеспечения параллельности и многоканальности, выделены жирным шрифтом):
// предшествующие общие строки #include не показаны
// это переопределение нужно для исключения предупреждений
// компилятора: 'THREAD_POOL_PARAM_T' redefined
#define THREAD_POOL_PARAM_T dispatch_context_t #include <sys/dispatch.h>
// следующее переопределение принципиально важно.
// оно предписывает вместо стандартного блока OCB (open control block),
// создаваемого вызовом клиента open() и соответствующего его файловому
// дескриптору, использовать собственную структуру данных.
// Эта структура <b>должна</b>быть производной от стандартной
// iofunc_ocb_t, а определение должно предшествовать
// включению <sys/iofunc.h>
<b>#define IOFUNC_OCB_T struct ownocb</b>
#include <sys/iofunc.h>
class ownocb public iofunc_ocb_t {
static const int BUFSIZE = 1024;
public:
char *buf;
ownocb(void) { buf = new char[BUFSIZE]; }
~ownocb(void) { delete buf; }
};
<b>IOFUNC_OCB_T *ownocb_calloc(resmgr_context_t *ctp, IOFUNC_ATTR_T *device) {</b>
<b> return new ownocb;</b>
<b>}</b>
<b>void ownocb_free(IOFUNC_OCB_T *o) { delete o; }</b>
<b>iofunc_funcs_t ownocb_funcs = {</b>
<b> _IOFUNC_NFUNCS, ownocb_calloc, ownocb_free</b>
<b>};</b>
<b>iofunc_mount_t mountpoint = { 0, 0, 0, 0, &ownocb_funcs };</b>
// Вместо умалчиваемой операции iofunc_lock_ocb_default(),
// вызываемой перед началом обработки запросов чтения/записи
// и блокирующей атрибутную запись, мы предписываем вызывать
// "пустую" операцию и не блокировать атрибутную запись,
// чем обеспечиваем параллелизм.