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

We use cookies. Read the Privacy and Cookie Policy

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

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

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().

Данный текст является ознакомительным фрагментом.