320 likes | 569 Views
НИТИ И СТАНДАРТНЫЕ БИБЛИОТЕКИ Unix. Программирование с использованием POSIX thread library. По завершении этого раздела вы сможете:. использовать стандартные библиотеки или их аналоги в многопоточных программах
E N D
НИТИ И СТАНДАРТНЫЕ БИБЛИОТЕКИ Unix Программирование с использованием POSIX thread library
По завершении этого раздела вы сможете: • использовать стандартные библиотеки или их аналоги в многопоточных программах • находить в документации информацию о том, является ли данная функция или группа функций thread-safe • использовать сигналы и fork в многопоточных программах
fork(2) в многопоточной среде • нити создаются в рамках процесса • fork(2) дублирует все состояние процесса • что происходит с нитями? • fork1 (дублируется только та нить, которая позвала fork) • forkall (дублируются все нити процесса) • в стандарте POSIX и Solaris 10fork(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 following attri- butes: ____________________________________________________________ | ATTRIBUTE TYPE | ATTRIBUTE VALUE | |_____________________________|_____________________________| | Interface Stability | See below. | |_____________________________|_____________________________| | MT-Level | See below. | |_____________________________|_____________________________| The strlcat() and strlcpy() functions are Stable. The remaining functions are Standard. The strtok() and strdup() functions are MT-Safe. The remain- ing 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 *restrictentry, struct dirent **restrict result); ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ успешное завершение – 0 ошибка - -1
Более сложный пример Int fd; fd=open(fname, flags); pthread_create(thread1, …); pthread_create(thread2, …); Thread1: lseek(fd, SEEK_SET, pos1); write(fd, buf, size); Thread2: 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 установлена