Выбрать главу

В этом месте внимательный читатель вправе оживиться: «Обманывают, обвешивают…». Да, описываемое здесь экзотическое решение не совсем корректно с позиции уже упоминавшегося определения Э. Дейкстры «слабосвязанных процессов» и независимости результата от относительных скоростей: в 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() и другие функции пассивной задержки) является операцией пассивного ожидания и должна сопровождаться принудительной диспетчеризацией.

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

void* ThreadProc(void* data) {

 struct sched_param param;

 int policy;

 pthread_getschedparam(pthread_self(), &policy, ¶m);

 param.sched_priority -= 2;

 // инициализация копии блока данных

 ...

 pthread_setschedparam(pthread_self(), policy, ¶m);

 ...

 return NULL;

}

...

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

 pthread_attr_t attr;

 pthread_attr_init(&attr);

 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

 pthread_attr_setschedpolicy(&attr, SCHED_RR);

 int policy;

 struct sched_param param;

 pthread_getschedparam(pthread_self(), &policy, ¶m);

 attr.param.sched_priority = param.sched_priority + 2;

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

}

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

Данные потока

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

class DataBlock {

 DataBlock(void);

 DataBlock(DataBlock&);

}

DataBlock A;

void* ThreadProc(void *data) {

 static DataBlock B;

 DataBlock C, D(*(DataBlock*)data);

 ...

 delete data;

 return NULL;

}

...

for(int i = 0; i < N; i++ ) {

 DataBlock E;

 // ... обработка и заполнение E ...

 pthread_create(NULL, NULL, &ThreadProc, new DataBlock(E));

}

В этом простейшем фрагменте кода N потоков разделяют единые экземпляры данных А и В: любые изменения, сделанные в данных потоком i, будут видимы потоку j, если, конечно, корректно выполнена синхронизация доступа к данным и потоки «совместными усилиями» не разрушат целостность блока данных. Другие блоки данных, С и D, представлены одним изолированным экземпляром на каждый поток, и никакие изменения, производимые потоком в своем экземпляре данных, не будут видны другим потокам.

Подобные эффекты не возникают в однопотоковых программах, а если они не учитываются и возникают спонтанно, то порождают крайне трудно выявляемые ошибки.[19] Очень часто такие ошибки возникают после преобразования корректных последовательных программ в потоковые. Рассмотрим простейший фрагмент кода:

int M = 0;

void Func_2(void) {

 static int С = 0;

 M += 2;

 C++;

 M -= 2;

}

void Func_1(void) { Func_2(); }

void* ThreadProc(void *data) {

 Func_1();

 return NULL;

}

...

for (int i = 0; i < N; i++)

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

полную версию книги