Чтение онлайн

на главную

Жанры

QNX/UNIX: Анатомия параллелизма
Шрифт:

}

...

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;

19

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

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
.

Собственные данные потока

Описанной выше схеме общих данных приложения и локальных данных потока, достаточных для большинства «ординарных» приложений, все-таки определенно не хватает гибкости, покрывающей все потребности. Поэтому в расширениях POSIX реального времени вводится третий специфичный механизм создания и манипулирования с данными в потоке — собственные данные потока (thread-specific data). Использование собственных данных потока — самый простой и эффективный способ манипулирования данными, представленными индивидуальными экземплярами данных для каждого потока.

Согласно POSIX операционная система должна поддерживать ограниченное количество объектов собственных данных (POSIX.1 требует, чтобы этот предел не превышал128 объектов на каждый процесс). Ядром системы поддерживается массив из этого количества ключей (тип

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

Рис. 2.4. Ключи экземпляров данных

Когда поток вызывает

pthread_key_create
для создания нового типасобственных данных, система разыскивает первое незанятое значение ключа и возвращает его значение (0...127). Для каждого потока процесса (в составе описателя потока) хранится массив из 128 указателей (
void*
) блоков собственных данных, и по полученному ключу поток, индексируя этот массив, получает доступ к своему экземпляру данных, ассоциированных со значением ключа. Начальные значения всех указателей блоков данных -
NULL
, а фактическое размещение и освобождение блоков данных выполняет пользовательская программа (рис. 2.5).

Рис. 2.5. Поток и его собственные данные

На рис. 2.5 представлен массив структур, создаваемый в единичном экземпляре для каждого процессабиблиотекой потоков. Каждый элемент ключа должен быть предварительно инициализирован вызовом

pthread_key_create
(однократно для всего процесса). Каждый инициализированный элемент массива определяет объекты единого класса во всех использующих их потоках, поэтому для них здесь же определяется деструктор (это в терминологии языка С!). Деструктор — единый для экземпляров данных в каждом потоке. Даже для инициализированного и используемого ключа в качестве деструктора может быть указан
NULL
, при этом никакие деструктивные действия при завершении потока не выполняются.

После размещения блока программа использует вызов

pthread_setspecific
. Для связывания адреса своего экземпляра данных с элементом массива указателей, индексируемого ключом. В дальнейшем каждый поток использует
pthread_getspecific
для доступа именно к своему экземпляру данных. Это схема, а теперь посмотрим, как она работает.

Положим, что нам требуется создать

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

class DataBlock {

~DataBlock { ... }

...

};

void* ThreadProc(void *data) {

// ... здесь будет код, который мы рассмотрим

return NULL;

}

...

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

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

Поделиться:
Популярные книги

Полковник Империи

Ланцов Михаил Алексеевич
3. Безумный Макс
Фантастика:
альтернативная история
6.58
рейтинг книги
Полковник Империи

Брачный сезон. Сирота

Свободина Виктория
Любовные романы:
любовно-фантастические романы
7.89
рейтинг книги
Брачный сезон. Сирота

Инферно

Кретов Владимир Владимирович
2. Легенда
Фантастика:
фэнтези
8.57
рейтинг книги
Инферно

Темный Охотник

Розальев Андрей
1. КО: Темный охотник
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Темный Охотник

Восьмое правило дворянина

Герда Александр
8. Истинный дворянин
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Восьмое правило дворянина

Неудержимый. Книга XI

Боярский Андрей
11. Неудержимый
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Неудержимый. Книга XI

Я — Легион

Злобин Михаил
3. О чем молчат могилы
Фантастика:
боевая фантастика
7.88
рейтинг книги
Я — Легион

Попаданка в Измену или замуж за дракона

Жарова Анита
Любовные романы:
любовно-фантастические романы
6.25
рейтинг книги
Попаданка в Измену или замуж за дракона

Ваше Сиятельство 2

Моури Эрли
2. Ваше Сиятельство
Фантастика:
фэнтези
альтернативная история
аниме
5.00
рейтинг книги
Ваше Сиятельство 2

Смертник из рода Валевских. Книга 1

Маханенко Василий Михайлович
1. Смертник из рода Валевских
Фантастика:
фэнтези
рпг
аниме
5.40
рейтинг книги
Смертник из рода Валевских. Книга 1

Титан империи 6

Артемов Александр Александрович
6. Титан Империи
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Титан империи 6

Цеховик. Книга 1. Отрицание

Ромов Дмитрий
1. Цеховик
Фантастика:
попаданцы
альтернативная история
5.75
рейтинг книги
Цеховик. Книга 1. Отрицание

Рота Его Величества

Дроздов Анатолий Федорович
Новые герои
Фантастика:
боевая фантастика
8.55
рейтинг книги
Рота Его Величества

Попаданка в деле, или Ваш любимый доктор

Марей Соня
1. Попаданка в деле, или Ваш любимый доктор
Фантастика:
фэнтези
5.50
рейтинг книги
Попаданка в деле, или Ваш любимый доктор