// непосредственно в параметр цикла!
int n = result.size();
for (int i = 0; i < n; i++) {
throwndata *d = result.front();
// обработка очередного блока *d ...
result pop();
delete d;
}
return EXIT_SUCCESS;
}
В предыдущих примерах кода мы указывали третий параметр
pthread_create()
в виде
&GetBlock
(адреса функции потока), но в текущем примере мы сознательно записали
GetBlock
. И то и другое верно, ибо компилятор достаточно умен, чтобы при указании имени функции взять ее адрес.
Собственные данные потоков — это настолько гибкий механизм, что он может таить в себе и другие, еще не используемые техники применения.
Безопасность вызовов в потоковой среде
Рассмотрев «в первом приближении» технику собственных данных потоков, мы теперь готовы ответить на вопрос: «В чем же главное предназначение такой в общем-то достаточно громоздкой техники? И зачем для ее введения потребовалось специально расширять стандарты POSIX?» Самое прямое ее предназначение, помимо других «попутных» применений, которые были обсуждены ранее, — это общий механизм превращения существующей функции для однопотокового исполнения в функцию, безопасную (thread safe) в многопоточном окружении. Этот механизм предлагает единую (в смысле «единообразную», а не «единственно возможную») технологию для разработчиков библиотечных модулей.
ОС QNX, заимствующая инструментарий GNU-технологии (gcc, make, …), предусматривает возможность построения как статически связываемых библиотек (имена файлов вида
xxx.a
), так и разделяемых или динамически связываемых (имена файлов вида
xxx.so
). Целесообразность последних при построении автономных и встраиваемых систем (на что главным образом и нацелена ОС QNX) достаточно сомнительна. Однако высказанное выше положение о построении реентерабельных программных единиц относится не только к библиотечным модулям (как статическим, так и динамическим) в традиционном понимании термина «библиотека», но и охватывает куда более широкий спектр возможных объектов и в той же мере относится и просто к любым наборам утилитных объектных модулей (вида
xxx.о
), разрабатываемых в ходе реализации под целевой программный проект.
Если мы обратимся к технической документации API QNX (аналогичная картина будет и в API любого UNIX), то заметим, что только небольшая часть функций отмечена как thread safe. К «небезопасным» отнесены такие общеизвестные вызовы, как
select()
,
rand()
и
readln()
, а многим «небезопасным» в потоковой среде вызовам сопутствуют их безопасные дубликаты с суффиксом
*_r
в написании имени функции, например
MsgSend()
—
MsgSend_r()
.
В чем же состоит небезопасность в потоковой среде? В нереентерабельности функций, подготовленных для выполнения в однопоточной среде, в первую очередь связанной с потребностью в статических данных, хранящих значение от одного вызова к другому. Рассмотрим классическую функцию
rand()
, традиционно реализуемую в самых разнообразных ОС примерно так (при «удачном» выборе констант
А
,
В
,
С
):
int rand(void) {
static int x = rand_init();
return x = (A*x + B)%C;
}