ИМЯ pthreads - нити POSIX
ОПИСАНИЕ В POSIX.1 определён набор интерфейсов (функции, заголовочные файлы) для работы с нитями, более известными как нити POSIX или Pthreads. В одном процессе может быть несколько нитей, которые выполняют одну программу. Эти нити работают с общей глобальной памятью (сегментами данных и кучи), но у каждой нити есть собственный стек (автоматические переменные).
Также, в POSIX.1 требуется, чтобы нити имели общий диапазон других атрибутов (т. е., эти атрибуты процесса, а не нити):
- идентификатор процесса
- идентификатор родительского процесса
- Идентификатор группы процессов и сеанса
- Управляющий терминал
- Идентификаторы пользователя и группы
- Открытые файловые дескрипторы
- Обычные блокировки (смотрите fcntl(2))
- Обработчики сигналов
- Маска создания режима доступа к файлу (umask(2))
- Текущий каталог (chdir(2)) и корневой каталог (chroot(2))
- Интервальные таймеры (setitimer(2)) и таймеры POSIX (timer_create(2))
- Значение уступчивости (setpriority(2))
- Ограничения по ресурсам (setrlimit(2))
- Измерители потребления времени ЦП (times(2)) и ресурсов (getrusage(2))
Как и для стека, в POSIX.1 определены другие атрибуты, которые уникальны в каждой нити:
- Идентификатор нити (тип данных pthread_t)
- Маска сигналов (pthread_sigmask(3))
- Переменная errno
- Альтернативный стек сигнала (sigaltstack(2))
- Алгоритм и приоритет планирования реального времени (sched(7))
Следующие свойства есть только в Linux и также уникальны в каждой нити:
- мандаты (смотрите capabilities(7))
Идентификатор нити Каждой нити процесса назначается уникальный идентификатор нити (имеет тип pthread_t). Этот идентификатор возвращается вызывающему pthread_create(3), а в самой нити её идентификатор можно получить с помощью pthread_self(3).
Внутри процесса гарантируется уникальность идентификаторов нитей (во всех функциях pthreads, которые принимают аргумент идентификатора нити, подразумевается, что указана нить из процесса вызывающего).
Система может повторно использовать идентификатор нити после объединения завершённой нити или отсоединения завершённой нити. В POSIX сказано: «Если приложение пытается использовать идентификатор нити, у который закончился срок жизни, то поведение не предсказуемо».
Нитебезопасные функции Нитебезопасная функция — это функция, которую можно безопасно (т. е., это приведёт к единым результатам независимо от окружения) вызывать из нескольких нитей одновременно.
В POSIX.1-2001 и POSIX.1-2008 требуется, чтобы все функции, описанные в стандарте, были нитебезопасными, за исключением следующих функций:
asctime() basename() catgets() crypt() ctermid() если передаётся аргумент не NULL ctime() dbm_clearerr() dbm_close() dbm_delete() dbm_error() dbm_fetch() dbm_firstkey() dbm_nextkey() dbm_open() dbm_store() dirname() dlerror() drand48() ecvt() [только POSIX.1-2001 (удалена из POSIX.1-2008)] encrypt() endgrent() endpwent() endutxent() fcvt() [только POSIX.1-2001 (удалена из POSIX.1-2008)] ftw() gcvt() [только POSIX.1-2001 (удалена из POSIX.1-2008)] getc_unlocked() getchar_unlocked() getdate() getenv() getgrent() getgrgid() getgrnam() gethostbyaddr() [только POSIX.1-2001 (удалена из POSIX.1-2008)] gethostbyname() [только POSIX.1-2001 (удалена из POSIX.1-2008)] getpwent() getpwnam() getpwuid() getservbyname() getservbyport() getservent() getutxent() getutxid() getutxline() gmtime() hcreate() hdestroy() hsearch() inet_ntoa() l64a() lgamma() lgammaf() lgammal() localeconv() localtime() lrand48() mrand48() nftw() nl_langinfo() ptsname() putc_unlocked() putchar_unlocked() putenv() pututxline() rand() readdir() setenv() setgrent() setkey() setpwent() setutxent() strerror() strsignal() [добавлена в POSIX.1-2008] strtok() system() [добавлена в POSIX.1-2008] tmpnam() если передаётся аргумент не NULL ttyname() unsetenv() wcrtomb() если конечный аргумент NULL wcsrtombs() если конечный аргумент NULL wcstombs() wctomb()
Безопасные асинхронные отменяемые функции Безопасная асинхронная отменяемая функция (async-cancel-safe function) — это функция, которую можно безопасно вызывать в приложении, в котором разрешено асинхронная отмена (смотрите pthread_setcancelstate(3)).
Согласно POSIX.1-2001 и POSIX.1-2008 только следующие функции должны быть безопасными асинхронными отменяемыми:
pthread_cancel()
Следующие функции должны быть точками отмены согласно POSIX.1-2001 и/или POSIX.1-2008:
accept() aio_suspend() clock_nanosleep() close() connect() creat() fcntl() F_SETLKW fdatasync() fsync() getmsg() getpmsg() lockf() F_LOCK mq_receive() mq_send() mq_timedreceive() mq_timedsend() msgrcv() msgsnd() msync() nanosleep() open() openat() [добавлена в POSIX.1-2008] pause() poll() pread() pselect() pthread_cond_timedwait() pthread_cond_wait() pthread_join() pthread_testcancel() putmsg() putpmsg() pwrite() read() readv() recv() recvfrom() recvmsg() select() sem_timedwait() sem_wait() send() sendmsg() sendto() sigpause() [только POSIX.1-2001 (перемещена в список «может» в POSIX.1-2008)] sigsuspend() sigtimedwait() sigwait() sigwaitinfo() sleep() system() tcdrain() usleep() [только POSIX.1-2001 (функция удалена в POSIX.1-2008)] wait() asctime() asctime_r() catclose() catgets() catopen() chmod() [добавлена в POSIX.1-2008] chown() [добавлена в POSIX.1-2008] closedir() closelog() ctermid() ctime() ctime_r() dbm_close() dbm_delete() dbm_fetch() dbm_nextkey() dbm_open() dbm_store() dlclose() dlopen() dprintf() [добавлена в POSIX.1-2008] endgrent() endhostent() endnetent() endprotoent() endpwent() endservent() endutxent() faccessat() [добавлена в POSIX.1-2008] fchmod() [добавлена в POSIX.1-2008] fchmodat() [добавлена в POSIX.1-2008] fchown() [добавлена в POSIX.1-2008] fchownat() [Added in POSIX.1-2008] fclose() fcntl() (для любого значения аргумента cmd) fflush() fgetc() fgetpos() fgets() fgetwc() fgetws() fmtmsg() fopen() fpathconf() fprintf() fputc() fputs() fputwc() fputws() fread() freopen() fscanf() fseek() fseeko() fsetpos() fstat() fstatat() [добавлена в POSIX.1-2008] getc_unlocked() getchar() getchar_unlocked() getcwd() getdate() getdelim() [добавлена в POSIX.1-2008] getgrent() getgrgid() getgrgid_r() getgrnam() getgrnam_r() gethostbyaddr() [только SUSv3 (функция удалена из POSIX.1-2008)] gethostbyname() [только SUSv3 (функция удалена из POSIX.1-2008)] gethostent() gethostid() gethostname() getline() [добавлена в POSIX.1-2008] getlogin() getlogin_r() getnameinfo() getnetbyaddr() getnetbyname() getnetent() getopt() (если opterr не равно 0) getprotobyname() getprotobynumber() getprotoent() getpwent() getpwnam() getpwnam_r() getpwuid() getpwuid_r() gets() getservbyname() getservbyport() getservent() getutxent() getutxid() getutxline() getwc() getwchar() getwd() [только SUSv3 (функция удалена из POSIX.1-2008)] glob() iconv_close() iconv_open() ioctl() link() linkat() [добавлена в POSIX.1-2008] lio_listio() [добавлена в POSIX.1-2008] localtime() localtime_r() lockf() [добавлена в POSIX.1-2008] lseek() lstat() mkdir() [добавлена в POSIX.1-2008] mkdirat() [добавлена в POSIX.1-2008] mkdtemp() [добавлена в POSIX.1-2008] pathconf() pclose() perror() popen() posix_fadvise() posix_fallocate() posix_madvise() posix_openpt() posix_spawn() posix_spawnp() posix_trace_clear() posix_trace_close() posix_trace_create() posix_trace_create_withlog() posix_trace_eventtypelist_getnext_id() posix_trace_eventtypelist_rewind() posix_trace_flush() posix_trace_get_attr() posix_trace_get_filter() posix_trace_get_status() posix_trace_getnext_event() posix_trace_open() posix_trace_rewind() posix_trace_set_filter() posix_trace_shutdown() posix_trace_timedgetnext_event() posix_typed_mem_open() printf() psiginfo() [добавлена в POSIX.1-2008] psignal() [добавлена в POSIX.1-2008] pthread_rwlock_rdlock() pthread_rwlock_timedrdlock() pthread_rwlock_timedwrlock() pthread_rwlock_wrlock() putc() putc_unlocked() putchar() putchar_unlocked() puts() pututxline() putwc() putwchar() readdir() readdir_r() readlink() [добавлена в POSIX.1-2008] readlinkat() [добавлена в POSIX.1-2008] remove() rename() renameat() [добавлена в POSIX.1-2008] rewind() rewinddir() scandir() [добавлена в POSIX.1-2008] scanf() seekdir() semop() setgrent() sethostent() strftime() symlink() symlinkat() [добавлена в POSIX.1-2008] sync() syslog() tmpfile() tmpnam() ttyname() ttyname_r() tzset() ungetc() ungetwc() unlink() unlinkat() [добавлена в POSIX.1-2008] utime() [добавлена в POSIX.1-2008] utimensat() [добавлена в POSIX.1-2008] utimes() [добавлена в POSIX.1-2008] vdprintf() [добавлена в POSIX.1-2008] vfprintf() vfwprintf() vprintf() vwprintf() wcsftime() wordexp() wprintf() wscanf()
Реализация также может помечать другие функции, не указанные в стандарте, как точки отмены. В частности, реализация, вероятно, пометит как точку отмены любую нестандартную функцию, которая может блокироваться (большинство функций, работающих с файлами).
Компиляция в Linux В Linux, программы, использующие программный интерфейс pthreads, должны компилироваться с помощью cc -pthread.
Реализации нитей POSIX в Linux За всё время в библиотеке GNU C было две реализации нитей для Linux:
LinuxThreads Первоначальная реализация pthreads. Начиная с glibc 2.4 эта реализация больше не поддерживается.
NPTL (библиотека нитей POSIX) Современная реализация pthreads. По сравнению с LinuxThreads, NPTL более точно соответствует требованиям POSIX.1 и более производительна при создании большого количества нитей. NPTL появилась в glibc начиная с версии 2.3.2, и требует свойства, появившиеся в ядре Linux 2.6.
Обе реализации являются, так называемыми реализациями 1:1, то есть каждая нить отображается в планируемый элемента ядра. Обе реализации используют системный вызов Linux clone(2). В NPTL примитивы синхронизации нитей (мьютексы, объединение нитей и т .п.) реализованы с помощью системного вызова Linux futex(2).
LinuxThreads Отличительные свойства данной реализации:
- У нитей разные ID процесса (фактически, нити LinuxThreads реализованы как процессы, у которых больше общей информации чем обычно, но которые имеют разный идентификаторы процесса). Нити LinuxThreads (включая управляющую нить) в ps(1) видимы как отдельные процессы.
Реализация LinuxThreads отклоняется от спецификации POSIX.1 в нескольких местах, а именно:
- Вызов getpid(2) возвращает разные значения для каждой нити.
- Вызов getppid(2) в нитях, кроме главной, возвращает идентификатор процесса управляющей нити; вместо getppid(2) в этих нитях будет возвращаться тоже значение, что и из getppid(2) в главной нити.
- Когда нить создаёт новый процесс-потомок с помощью fork(2), все нити должны ожидать потомка в wait(2). Однако реализация позволяет вызвать wait(2) только в нити, которая создала потомка.
- Когда нить вызывает execve(2), остальные нити завершают работу (требование POSIX.1). Однако, получаемый процесс имеет тот же PID, что и нить, которая вызвала execve(2): это должен быть тот же PID, что и у главной нити.
- У нитей разные ID пользователя и группы. Это может вызвать сложности в программах с set-user-ID и может привести к ошибкам функций pthreads, если приложение изменяет свои учётные данные с помощью seteuid(2) и подобных вызовов.
- У нитей разные ID сеанса и группы процессов.
- У нитей разные записи о блокировках, созданных fcntl(2).
- Информация, возвращаемая times(2) и getrusage(2), относится только к нити, а не к процессу в целом.
- У нитей разные значения отмен семафоров (смотрите semop(2)).
- У нитей разные интервалы таймеров.
- У нитей разные значения уступчивости.
- В POSIX.1 различаются сигналы, адресованные процессу в целом и отдельным нитям. Согласно POSIX.1, сигналы, направленные процессу (посланные, например, с помощью kill(2)), должны обрабатываться одной произвольно выбранной нитью внутри процесса. LinuxThreads не поддерживает сигналы, направленные процессу: сигналы могут посылаться только определённым нитям.
- Нити имеют разные настройки альтернативного стека сигналов. Однако, новые настройки альтернативного стека сигналов копируются из нити, которая его создаёт, так что изначально нити имеют единый альтернативный стек сигналов (новая нить должна запускаться без альтернативного стека сигналов. Если две нити обрабатывают сигналы в едином альтернативном стеке сигналов одновременно, то в программе возникнет непредсказуемая ошибка).
NPTL В NPTL все нити процесса помещаются в одну группу нитей; все члены группы нитей имеют один PID. В NPTL нет управляющей нити.
- Информация, возвращаемая times(2) и getrusage(2), относится только к нити, а не к процессу в целом (исправлено в ядре 2.6.9).
- У нитей разные ограничения по ресурсам (исправлено в ядре 2.6.10).
- У нитей разные интервалы таймеров (исправлено в ядре 2.6.12).
- Только из главной нити разрешено запускать новый сеанс с помощью setsid(2) (исправлено в версии 2.6.16).
- Только из главной нити разрешено делать процесс лидером группы процессов с помощью setpgid(2) (исправлено в версии 2.6.16).
- Нити имеют разные настройки альтернативного стека сигналов Однако, новые настройки альтернативного стека сигналов копируются из нити, которая его создаёт, так что изначально нити имеют единый альтернативный стек сигналов (исправлено в ядре 2.6.16).
Также стоит учитывать следующее о реализации NPTL:
- Если мягкое ограничение ресурса на размер стека (смотрите описание RLIMIT_STACK в setrlimit(2)) устанавливается в значение, отличное от unlimited, то это значение определяет размер стека по умолчанию для новых нитей. В целях эффективности, это ограничение должно быть установлено но выполнения программы, возможно с помощью встроенной команды оболочки ulimit -s (limit stacksize в оболочке C).
Определение реализации нитей Начиная с glibc 2.3.2, для определение реализации нитей в системе можно использовать команду getconf(1), например:
bash$ getconf GNU_LIBPTHREAD_VERSION NPTL 2.3.4
При наличии старых версий glibc можно использовать команду:
bash$ $( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | \ egrep -i 'threads|nptl' Native POSIX Threads Library by Ulrich Drepper et al
Выбор реализации нитей: LD_ASSUME_KERNEL В системах с glibc, которая поддерживает и LinuxThreads и NPTL (например, glibc 2.3.x), можно воспользоваться переменной окружения LD_ASSUME_KERNEL для замены выбранной динамическим компоновщиков реализации нитей по умолчанию. Эта переменная указывает динамическому компоновщику считать, что он запускается с определённой версией ядра в системе. Указав версию ядра, в которой не поддержки, требуемой NPTL, его можно заставить использовать LinuxThreads (наиболее вероятной причиной для этого будет необходимость запуска (сломанного) приложения, которое зависит от некоторого не совместимого поведения LinuxThreads). Пример:
bash$ $( LD_ASSUME_KERNEL=2.2.5 ldd /bin/ls | grep libc.so | \ awk '{print $3}' ) | egrep -i 'threads|nptl' linuxthreads-0.10 by Xavier Leroy
СМОТРИТЕ ТАКЖЕ clone(2), fork(2), futex(2), gettid(2), proc(5), attributes(7), futex(7), nptl(7), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3) и pthread_testcancel(3)
|