void *pthread_getspecific(pthread_key_t
Возвращает указатель или NULL, если с ключом не связаны данные уровня потока
Аргумент value, передающийся функции pthread_setspecific(), обычно является указателем на блок памяти, ранее выделенный вызывающим потоком. При завершении потока этот указатель будет передан в функцию-деструктор для текущего ключа.
Аргумент value не обязательно должен быть указателем на блок памяти. Он может представлять собой некое скалярное значение, которое можно привести к типу void *. В этом случае в ранее сделанном вызове функции pthread_key_create() аргумент destructor должен равняться NULL.
На рис. 31.3 показана типичная реализация структуры данных, которая используется для хранения value. На этой диаграмме подразумевается, что элемент pthread_keys[1] был выделен функции с именем myfunc(). Программный интерфейс Pthreads предоставляет массив указателей на блоки данных каждого отдельного потока. Элементы этих массивов напрямую соответствуют элементам глобального массива pthread_keys, показанного на рис. 31.2. Функция pthread_setspecific() привязывает каждый элемент массива к соответствующему ключу вызывающего потока.
При создании потока указатели на все его данные инициализируются значением NULL. Это означает, что, когда поток впервые вызывает нашу функцию, он должен сначала сделать вызов pthread_getspecific(), чтобы проверить, имеет ли он значение, связанное с ключом. Если такого значения нет, функция выделяет блок памяти и сохраняет указатель на него с помощью вызова pthread_setspecific(). В следующем разделе эта процедура будет продемонстрирована на примере реализации потокобезопасной разновидности функции strerror().
Рис. 31.3.
31.3.4. Использование программного интерфейса для работы с данными уровня потока
При первом знакомстве со стандартной функцией strerror() (см. раздел 3.4) мы отмечали, что она может вернуть указатель на статически выделенную строку. Это означает, что данная функция может не быть потокобезопасной. На нескольких следующих страницах мы рассмотрим стандартную реализацию strerror() и затем увидим, как сделать ее потокобезопасной с помощью данных уровня потока.
Во многих реализациях UNIX, включая Linux, функция strerror(), входящая в состав стандартной библиотеки С, является потокобезопасной. Она выбрана в качестве демонстрации, потому что стандарт SUSv3 не требует применять к ней правила потоковой безопасности; к тому же ее реализация послужит простым примером использования данных уровня потока.
В листинге 31.1 показана простая реализация strerror(), которая не является потокобезопасной. Она задействует две глобальные переменные, объявленные библиотекой glibc: одна, _sys_errlist, представляет собой массив указателей на строки, соответствующие номерам ошибок в errno (хотя элемент _sys_errlist[EINVAL], к примеру, указывает на строку Invalid operation), а другая, _sys_nerr, указывает, сколько элементов находится в _sys_errlist.
Листинг 31.1. Реализация strerror() без учета потоковой безопасности
threads/strerror.c
#define _GNU_SOURCE /* Получаем объявления '_sys_nerr'
и '_sys_errlist' из файла
#include
#include
#define MAX_ERROR_LEN 256 /* Максимальная длина строки,
возвращаемой функцией strerror() */
static char buf[MAX_ERROR_LEN]; /* Статически выделенный итоговый буфер */
char *
strerror(int err)
{
if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN — 1);
buf[MAX_ERROR_LEN — 1] = '\0'; /* Завершаем строку символом '\0' */
}
return buf;
}
threads/strerror.c
На примере данной программы можно продемонстрировать последствия того, что реализация strerror() из листинга 31.1 пренебрегает потоковой безопасностью. Эта программы вызывает strerror() из двух разных потоков, но выводит возвращаемое значение только после того, как оба потока завершили выполнение этой функции. И хотя каждый поток передает функции strerror() разные значения (EINVAL и EPERM), полученный результат будет следующим (при условии, если мы скомпилируем и скомпонуем программу с версией strerror() из листинга 31.1):
$ ./strerror_test
Main thread has called strerror()
Other thread about to call strerror()
Other thread: str (0x804a7c0) = Operation not permitted
Main thread: str (0x804a7c0) = Operation not permitted