НИТИ И СТАНДАРТНЫЕ БИБЛИОТЕКИ Unix Программирование с использованием POSIX thread

advertisement
НИТИ И СТАНДАРТНЫЕ
БИБЛИОТЕКИ Unix
Программирование с
использованием POSIX thread
library
По завершении этого раздела вы
сможете:
• использовать стандартные библиотеки
или их аналоги в многопоточных
программах
• находить в документации информацию
о том, является ли данная функция или
группа функций thread-safe
• использовать сигналы и fork в
многопоточных программах
fork(2) в многопоточной среде
• нити создаются в рамках процесса
• fork(2) дублирует все состояние процесса
• что происходит с нитями?
– fork1 (дублируется только та нить, которая позвала
fork)
– forkall (дублируются все нити процесса)
• в стандарте POSIX и Solaris 10 fork(2)≡fork1
• в старых версиях Solaris, fork(2)≡forkall.
• в других реализациях POSIX threads
эквивалента forkall может вообще не быть
fork1(2)
• дублируется только нить, вызвавшая
fork1(2)
• сохраняются все блокировки,
установленные остальными нитями (в
том числе блокировки, скрытые внутри
библиотечных функций)
• вызов таких функций может привести к
мертвой блокировке (будете ждать нити,
которой не существует)
pthread_atfork(3C)
ИСПОЛЬЗОВАНИЕ
#include <sys/types.h>
#include <unistd.h>
int pthread_atfork(void (*prepare) (void),
void (*parent) (void),
void (*child) (void));
ОПИСАНИЕ
Регистрирует обработчики, вызываемые перед fork1
(prepare) и после него (parent, child).
Порядок вызовов atfork имеет значение.
Обработчики prepare вызываются в порядке LIFO,
обработчики parent/child в порядке FIFO.
Как сделать библиотеку fork-safe
• Определите все блокировки, используемые
библиотекой и порядок их захвата (L1..Ln)
• Напишите функции f1, f2 и f3
– f0() { lock(L1); … lock(Ln); }
– fp() { unlock(L1); … unlock(Ln); }
– fc() { unlock(L1); … unlock(Ln); }
• Включите вызов pthread_atfork(f0, fp, fc) в код
инициализации библиотеки (секцию .init для
библиотек ELF)
Почему так?
• Если функция использует блокировку,
значит, она использует внутренние
данные, которые могут быть в
несогласованном состоянии
• Прежде чем снимать блокировку, нам
нужно дождаться завершения этой
функции (если она вызвана в какой-то
другой нити).
Сигналы и потоки
• Сигналы делятся на синхронные и
асинхронные
• Синхронные сигналы возникают при
исполнении определенного кода в
вашей программе (напр. SIGFPE при
делении на 0)
• Асинхронные сигналы возникают по
внешним причинам
Сигналы и потоки (продолжение)
• синхронные сигналы обрабатываются в
том потоке, в котором возникли
• асинхронные сигналы обрабатываются
в любом потоке
• необработанные сигналы вызывают
реакцию по умолчанию для всего
процесса (завершение всего процесса,
засыпание всего процесса и т.д.)
signal(2) и sigset(2)
• вызовы signal(2) и sigset(2)
устанавливают глобальный обработчик
сигнала (во всех нитях процесса)
• установить собственный обработчик
сигнала нить не может.
Проблемы, связанные с
сигналами
• возможность мертвой блокировки
– нить держит блокировку
– прилетает сигнал
– обработчик сигнала вызывает функцию,
которая пытается захватить ту же
блокировку
– нить ждет сама себя
• Атрибут MT-Level==Async-Signal-Safe
Проблемы, связанные с
сигналами (продолжение)
• вызов setjmp/longjmp
– если setjmp вызывался в одной нити,
– а longjmp в другой
– это приведет к разрушению стека (скорее
всего, SIGSEGV, но не обязательно,
программа может исполнить какой-то еще
код и, например, записать мусор в файлы и
т.д.)
pthread_sigmask(3C)
ИСПОЛЬЗОВАНИЕ
#include <pthread.h>
#include <signal.h>
int pthread_sigmask(int how,
const sigset_t *set,
sigset_t
*oset);
ОПИСАНИЕ
функционально аналогична sigprocmask(2),
но устанавливает маску сигналов нити
маска сигналов нити наследуется при
pthread_create
sigprocmask/pthread_sigmask how
SIG_BLOCK - Множество сигналов, на
которое указывает set, будет добавлено
к текущей маске.
SIG_UNBLOCK - Множество set будет
удалено из текущей маски.
SIG_SETMASK - Текущая маска будет
заменена на set.
sigsetops(3C)
ИСПОЛЬЗОВАНИЕ
#include <signal.h>
int sigemptyset(sigset_t * set);
int sigfillset(sigset_t * set);
int sigaddset(sigset_t * set, int signo);
int sigdelset(sigset_t * set, int signo);
int sigismember(sigset_t * set, int signo);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
успех - sigismember: 1 если истинно, 0 если ложно;
остальные функции: 0
неуспех - -1 и errno установлена
ЧТО ОЗНАЧАЕТ THREAD-SAFE ?
• словосочетание thread-safe (MT-safe) не
имеет общепринятого русского перевода
• дословный перевод – безопасно [для]
использования в многопоточной программе
• стандартные библиотеки Unix и ANSI C
разрабатывались до появления
многопоточности
• не все функции стандартных библиотек
корректно работают в многопоточной среде
Пример – strtok(3C)
ИСПОЛЬЗОВАНИЕ
#include <strings.h>
char *strtok(char *restrict s1,
const char *restrict s2);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
при первом вызове, возвращает первое слово
в строке s1, используя разделители,
указанные в s2. При втором вызове
возвращает второе слово и т.д.
Если достигнут конец строки, возвращает
NULL
Пример – strtok_r(3C)
ИСПОЛЬЗОВАНИЕ
#include <strings.h>
char *strtok_r(char *restrict s1,
const char *restrict s2,
char **lasts);
ПРИМЕЧАНИЕ
хранит состояние строки в ячейке памяти, на
которую указывает параметр lasts
Примечание
• В действительности, strtok в Solaris
использует thread-specific data для
хранения указателя на строку, поэтому
в Solaris эта функция thread-safe. Но
это не обязательно верно для других
реализаций.
Как узнать, является ли функция
thread-safe?
• Секция ATTRIBUTES в странице руководства
ATTRIBUTES
See attributes(5) for descriptions of the
butes:
following
attri-
____________________________________________________________
|
ATTRIBUTE TYPE
|
ATTRIBUTE VALUE
|
|_____________________________|_____________________________|
| Interface Stability
| See below.
|
|_____________________________|_____________________________|
| MT-Level
| See below.
|
|_____________________________|_____________________________|
The strlcat() and strlcpy() functions
remaining functions are Standard.
are
Stable.
The
The strtok() and strdup() functions are MT-Safe. The remaining functions are Async-Signal-Safe.
Значения атрибута MT-level
•
•
•
•
•
•
•
•
Unsafe
Safe
MT-Safe
Async-Signal-Safe
MT-Safe with exceptions
Safe with exceptions
Fork-Safe
Cancel-Safety (Deferred- и Asynchronous-)
MT Level Unsafe
• Функция или группа функций использует
статические или глобальные
переменные, не защищенные
примитивами синхронизации.
• Требует явной защиты мутексами или
другими средствами синхронизации
MT Level Safe
• Функции библиотеки сами по себе
реентерабельны, но могут обеспечивать
недостаточный уровень параллелизма.
• Например, Safe функция может
использовать внутренние мутексы,
удерживаемые длительное время или в
удерживаемые в промежутках между
вызовами функций
MT Level MT-Safe
• Функция или группа функций полностью
готова для работы в многопоточной
среде.
• Обеспечивает разумный уровень
параллелизма, т.е оптимизирована так,
чтобы удерживать внутренние
блокировки (если они есть) минимально
возможное время
MT Level Async-Signal-Safe
• Подразумевает MT-Safe
• Может вызываться из обработчика
сигнала.
• Для MT-Safe это не всегда так. Если
MT-Safe функция держит блокировку и в
это время в той же нити вызовется
обработчик сигнала, это может
привести к мертвой блокировке.
MT Level Fork-Safe
• подразумевает MT-Safe
• при fork(2) в дочернем процессе
остается только та нить, которая
вызвала fork
• блокировки, удерживаемые
исчезнувшими нитями, остаются
• если библиотека MT-Safe за счет
использования блокировок, она может
быть не Fork-Safe
Пример – readdir(3C)
ИСПОЛЬЗОВАНИЕ
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Возвращает указатель на статический
буфер, перезаписывает его при
последующих вызовах.
readdir_r(3C)
ИСПОЛЬЗОВАНИЕ
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir_r(DIR *dirp,
struct dirent *entry);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
Возвращает NULL, если достигнут
конец каталога, и entry, если есть
следующая запись
readdir_r(3C) – стандартная
форма
ИСПОЛЬЗОВАНИЕ
#include <sys/types.h>
#include <dirent.h>
cc file ... -D_POSIX_PTHREAD_SEMANTICS
int readdir_r(DIR *restrict dirp,
struct dirent *restrict entry,
struct dirent **restrict result);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
успешное завершение – 0
ошибка - -1
Более сложный пример
Int fd;
fd=open(fname, flags);
pthread_create(thread1, …);
pthread_create(thread2, …);
Thread1:
Thread2:
lseek(fd, SEEK_SET, pos1);
write(fd, buf, size);
lseek(fd, SEEK_SET, pos2);
write(fd, buf2, size);
pread (2)
ИСПОЛЬЗОВАНИЕ
#include <unistd.h>
int pread( int fildes,
void *buf, unsigned nbyte,
off_t offset);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
успех - количество прочитанных байт
неуспех - -1 и errno установлена
не перемещает указатель в файле
pwrite (2)
ИСПОЛЬЗОВАНИЕ
#include <unistd.h>
int pwrite( int fildes, const void
*buf, unsigned nbyte,
off_t offset);
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
успех - количество записанных байт
неуспех - -1 и errno установлена
Download