QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович
Шрифт:
Интервал:
Закладка:
5. Создание потока с приоритетом выше, чем родительский, с последующим возвратом его приоритета на прежний уровень после выполнения требуемой инициализации копии:
void* ThreadProc(void* data) {
struct sched_param param;
int policy;
pthread_getschedparam(pthread_self(), &policy, &param);
param.sched_priority -= 2;
// инициализация копии блока данных
...
pthread_setschedparam(pthread_self(), policy, &param);
...
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, &param);
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);
Можно ли здесь утверждать, что переменная Mсохранит нулевое значение, а переменная Сдействительно является счетчиком вызовов и ее результирующее значение станет N? Ни в коей мере: после выполнения такого фрагмента в переменных может быть все что угодно. Но цепочка вызовов Func_1()->Func_2()может быть сколь угодно длинной, описание Func_2()может находиться совершенно в другом файле кода (вместе с объявлением переменной M!) и, наконец, Func_2()в нашей транскрипции может быть любой функцией из библиотек C/C++, писавшейся лет 15 назад и содержащей в своем теле статические переменные!
POSIX.1 требует, чтобы определенные в нем функции были максимально безопасными в многопоточной среде. Но переработка всех библиотек - трудоемкий и длительный процесс. API QNX (и так поступили производители многих POSIX-совместимых ОС) для потенциально небезопасных в многопоточной среде функций ввели их эквиваленты, отличающиеся суффиксом «_r», например: localtime()— localtime_r(), rand()— rand_r()и т.д. Принципиально небезопасна в многопоточной среде одна из самых «любимых» в UNIX функция — select().