Изменить стиль страницы

if ( /* нечто */ ) {

 // static обеспечивает неразрушаемость

 static struct DataParam copy;

 copy = data;

 pthread_create(NULL, &attr, &ThreadProc, &copy);

}

Этот способ иногда хорошо «срабатывает» для данных типа символьных строк, представленных в стандарте языка С (однако используется он не часто):

void* ThreadProc(void *data) {

 ...

 // можно даже не делать копию - это уже копия:

 printf("%s", (char*)data);

}

...

while (true) {

 char *data = ... /* инициализация данных */;

 if ( /* нечто */ )

  pthread_create(NULL, &attr, &ThreadProc, strdup(data));

}

2. Для передачи параметра скалярного типа (

char
,
short
,
int
), не превышающего размер указателя, очень часто в самых разнообразных источниках [1, 3] можно увидеть такой трюк, когда указателю присваивается непосредственное значение скалярной величины:

// функция потока:

void* ThreadProc(void* data) {

 // ... выполняется обработка, используя значение параметра (char)data

 return NULL;

}

// порождающий потоки код:

while (true) {

 char data = /* инициализация параметра */;

 if ( /* ожидаем нечто */ )

  pthread_create(NULL, &attr, &ThreadProc, (void*)data);

}

Или даже так:

pthread_create(NULL, &attr, &ThreadProc, (void*)5);

pthread_create(NULL, &attr, &ThreadProc, (void*)(x + y));

Положительной стороной этого решения (которое тем не менее остается трюкачеством) является то, что параметр в

ThreadProc()
передается по значению, то есть неявным копированием, и любые последующие манипуляции с ним не приведут к порче переданного значения. Таким образом, в
ThreadProc()
нет необходимости создавать локальную копию полученного параметра.

3. Создание экземпляра данных в родительскомпотоке для каждого нового экземпляра создаваемого потока сгарантированным уничтожением экземпляра данных при завершении порожденногопотока:

void* ThreadProc(void *data) {

 // используем экземпляр data без копирования ...

 ...

 delete data;

 return NULL;

}

...

if ( /* нечто */ ) {

 // создание экземпляра вместе с инициализацией

 // (предполагаем, что для DataParam ранее определен

 // копирующий конструктор):

 pthread_create(NULL, &attr, &ThreadProc, new DataParam(data));

}

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

4. «Ручной» вызов диспетчеризации в порождающем потоке, по крайней мере при дисциплине по умолчанию для QNX — round-robin:

if ( /* нечто */ ) {

 pthread_create(NULL, &attr, &ThreadProc, &data);

 sched_yield();

}

Мы не можем произвольно изменять последовательность выполнения потоков (чем нарушили бы принципы диспетчеризации) и не можем утверждать, что при наличии многих потоков именно только что порожденный поток получит управление. Но после выполнения

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

Примечание

В этом месте внимательный читатель вправе оживиться: «Обманывают, обвешивают…». Да, описываемое здесь экзотическое решение не совсем корректно с позиции уже упоминавшегося определения Э. Дейкстры «слабосвязанных процессов» и независимости результата от относительных скоростей: в SMP-системе при количестве процессоров, большем, чем количество параллельных потоков, это решение не будет работать так, как мы ему предписываем. Но к настоящему времени такое «стечение обстоятельств» может быть либо чисто теоретически умозрительным, либо возникать на экспериментальных единичных образцах SMP, содержащих десятки и сотни процессоров…, но где QNX, насколько нам известно, не используется.

В этом варианте и в порожденном потоке можно симметрично сделать так:

void* ThreadProc(void *data) {

 struct DataParam copy(*data);

 sched_yield();

 ...

}

Примечание

Иногда для выражения этой техники используется и такая, в общем несколько небрежная, форма записи:

pthread_create(NULL, &attr, &ThreadProc, &data);

delay(1); // вместо sched_yield()

Фокус здесь состоит не в том, что 1 миллисекунда — это время, заведомо достаточное для копирования экземпляра данных, а в том, что POSIX определяет, что операция

delay()
(а также все родственные ей функции:
sleep()
,
nanosleep()
и другие функции пассивной задержки) является операцией пассивногоожидания и должна сопровождаться принудительной диспетчеризацией.