UNIX: разработка сетевых приложений
Рис. 26.5. Структуры данных после того, как поток n инициализировал свои собственные данные
6. Поток номер n продолжает выполнять функцию
, используя и модифицируя свои собственные данные.readlineОдин вопрос, который мы пока не рассмотрели, заключается в следующем: что происходит, когда поток завершает свое выполнение? Если поток вызвал функцию
, эта функция выделила в памяти область, которая должна быть освобождена по завершении выполнения потока. Для этого используется указатель-деструктор, показанный на рис. 26.2. Когда поток, создающий элемент собственных данных, вызывает функциюreadline, одним из аргументов этой функции является указатель на функцию-деструктор. Когда выполнение потока завершается, система перебирает массивpthread_key_createдля данного потока, вызывая соответствующую функцию-деструктор для каждого непустого указателяpkey. Под «соответствующим деструктором» мы понимаем указатель на функцию, хранящийся в массивеpkeyс рис. 26.2. Таким образом осуществляется освобождение памяти, занимаемой собственными данными потока, когда выполнение потока завершается.KeyПервые две функции, которые обычно вызываются при работе с собственными данными потока, — это
иpthread_once.pthread_key_create#include <pthread.h>int pthread_once(pthread_once_t *<i>onceptr</i>, void (*<i>init</i>)(void));int pthread_key_create(pthread_key_t *<i>keyptr</i>, void (*<i>destructor</i>)(void *<i>value</i>));<i>Обе функции возвращают: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</i>Функция
обычно вызывается при вызове функции, манипулирующей собственными данными потока, ноpthread_onceиспользует значение переменной, на которую указываетpthread_once, чтобы гарантировать, что функцияonceptrвызывается для каждого процесса только один раз.initФункция
должна вызываться только один раз для данного ключа в пределах одного процесса. Значение ключа возвращается с помощью указателяpthread_key_create, а функция-деструктор (если аргумент является непустым указателем) будет вызываться каждым потоком по завершении его выполнения, если этот поток записывал какое-либо значение, соответствующее этому ключу.keyptrОбычно эти две функции используются следующим образом (если игнорировать возвращение ошибок):
pthread_key_t rl_key;pthread_once_t rl_once = PTHREAD_ONCE_INIT;void readline_destructor(void *ptr) {free(ptr);}void readline_once(void) {pthread_key_create(&rl_key, readline_destructor);}ssize_t readline(...) {...pthread_once(&rl_once, readline_once);if ((ptr = pthread_getspecific(rl_key)) == NULL) {ptr = Malloc(...);pthread_setspecifiс(rl_key, ptr);/* инициализация области памяти, на которую указывает ptr */}.../* используются значения, на которые указывает ptr */}Каждый раз, когда вызывается функция
, она вызывает функциюreadline. Эта функция использует значение, на которое указывает ее аргумент-указательpthread_once(содержащийся в переменнойonceptr), чтобы удостовериться, что функцияrl_onceвызывается только один раз. Функция инициализацииinitсоздает ключ для собственных данных потока, который хранится вreadline_onceи который функцияrl_keyзатем использует в вызовах функцийreadlineиpthread_getspecific.pthread_setspecificФункции
иpthread_getspecificиспользуются для того, чтобы получать и задавать значение, ассоциированное с данным ключом. Это значение представляет собой тот указатель, который показан на рис. 26.3. На что указывает этот указатель — зависит от приложения, но обычно он указывает на динамически выделяемый участок памяти.pthread_setspecific#include <pthread.h>void *pthread_getspecific(pthread_key_t <i>key</i>);<i>Возвращает: указатель на собственные данные потока (возможно, пустой указатель)</i>int pthread_setspecific(pthread_key_t <i>key</i>, const void *<i>value</i>);<i>Возвращает: 0 в случае успешного выполнения, положительное значение Exxx в случае ошибки</i>Обратите внимание на то, что аргументом функции
является указатель на ключ (поскольку эта функция хранит значение, присвоенное ключу), в то время как аргументами функцийpthread_key_createиgetявляются сами ключи (которые, скорее всего, представляют собой небольшие целые числа, как уже говорилось).setПример: функция readline, использующая собственные данные потока
В этом разделе мы приводим полный пример использования собственных данных потока, преобразуя оптимизированную версию функции
из листинга 3.12 к виду, безопасному в многопоточной среде, не изменяя последовательность вызовов.readlineВ листинге 26.5 показана первая часть функции: переменные
иpthread_key_t, функцииpthread_once_tиreadline_destructorи наша структураreadline_once, которая содержит всю информацию, нужную нам для каждого потока.Rline
