Создание и завершение нитей Программирование с использованием POSIX thread library 2006-2007 Иртегов Д.В. Учебное пособие подготовлено по заказу и при поддержке ООО «Сан Майкросистемс СПБ» В ходе этой лекции вы изучите • Создание нитей с атрибутами по умолчанию • Передачу параметров нити • Завершение нити • Ожидание завершения другой нити • Принудительное завершение нити • Обработку принудительного завершения нити pthread_create(3C) #include <pthread.h> int pthread_create( pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg); ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ Код ошибки, 0 при успешном завершении Параметры • pthread_t * thread – Выходной параметр. Указатель на переменную, в которой при успешном завершении будет размещен идентификатор нити. • const pthread_attr_t * attr – Входной параметр. Указатель на структуру, в которой заданы атрибуты нити (рассматривается на следующей лекции). Если этот указатель равен NULL, используются атрибуты по умолчанию. • void *(*start_routine)(void*) – Входной параметр. Указатель на функцию, которая будет запущена во вновь созданной нити. • void * arg – Входной параметр. Значение, которое будет передано в качестве параметра start_routine. pthread_t • • • • Непрозрачный тип В Solaris – небольшие целые числа В Linux 2.4 – pid В Linux 2.6 – указатель (необходимо проявлять осторожность при работе с неинициализированным pthread_t или идентификаторами завершенных нитей) Передача параметров нити • Параметр имеет тип void * • Система никогда не обращается к нему как к указателю (через него можно передавать скаляры) • При передаче параметров есть две опасности: – Утечка памяти – Висячие ссылки Выделение памяти под параметры в стеке • Следует проявлять осторожность при передаче параметров, размещенных в стеке • Это можно делать только если родительская нить делает pthread_join(3C) (иначе есть риск, что родительская нить завершится раньше, чем дочерняя доберется до параметров) Выделение памяти под параметры через malloc(3C) • Нить должна либо освобождать блок параметров сама – Дурной тон (считается неправильным, когда функция знает, как выделялась память под ее параметры) • Либо возвращать его в коде возврата (тогда родитель должен делать pthread_join(3C) и освобождать ее) Выделение памяти под параметры статически • Проще всего • Ограничивает количество нитей, которые вы можете создать – Не всегда приемлемо Pthread_exit(3C) #include <pthread.h> void pthread_exit(void *value_ptr); Pthread_exit(3C) • В основном эквивалентна return val; в start_routine • В некоторых комбинациях libpthread/C++ компилятора не вызывает деструкторы локальных переменных (хотя по идее должна вызывать) exit(2) в многопоточной программе • exit(2) завершает процесс (все нити) • return val; в main эквивалентен exit(2) • Если хотите, чтобы нити вашей программы исполнялись после завершения main, необходимо завершать main по pthread_exit(3C) exit(2) и С++ • exit(2) вызывает деструкторы статических переменных и обработчики atexit(3C) • При этом во многих реализациях С++ нити еще продолжают работать. – Обращение к статическим переменным приведет к проблемам вплоть до SIGSEGV • _exit(2) не вызывает деструкторы и atexit(3C) pthread_join(3C) #include <pthread.h> int pthread_join( pthread_t thread, void **status); status==NULL – игнорировать код возврата Ожидание завершения нити • Любая нить может ждать завершения любой нити того же процесса (в отличие от wait(2), который может делать только родитель) • К моменту разблокировки pthread_join(3C), стек и TLD уже уничтожены • Если несколько нитей ждут одну, только одна из них получает код возврата, остальные – ESRCH • Если нить ждет сама себя, ошибка EDEADLK pthread_detach(3C) #include <pthread.h> int pthread_detach( pthread_t thread); Можно запускать нить отсоединенной (атрибут detachstate) pthread_cancel(3C) #include <pthread.h> int pthread_cancel( pthread_t target_thread); int pthread_setcancelstate( int state, int *oldstate); int pthread_setcanceltype( int type, int *oldtype); Cancel state/type • State (pthread_setcancelstate(3C)) – PTHREAD_CANCEL_ENABLE (разрешено) – PTHREAD_CANCEL_DISABLE (запрещено) • Type (pthread_setcanceltype(3C)) – PTHREAD_CANCEL_ASYNCHRONOUS (в любой момент) – PTHREAD_CANCEL_DEFERRED (только в точках прерывания) Точки прерывания • Man cancellation(5) • pthread_testcancel(3C) • aio_suspend(3RT), close(2), creat(2), getmsg(2), getpmsg(2), lockf(3C), mq_receive(3RT), mq_send(3RT), msgrcv(2), msgsnd(2), msync(3C), nanosleep(3RT), open(2), pause(2), poll(2), pread(2), pthread_cond_timedwait(3C), pthread_cond_wait(3C), pthread_join(3C), pthread_testcancel(3C), putmsg(2), putpmsg(2), pwrite(2), read(2), readv(2), select(3C), sem_wait(3RT), sigpause(3C), sigwaitinfo(3RT), sigsuspend(2), sigtimedwait(3RT), sigwait(2), sleep(3C), sync(2), system(3C), tcdrain(3C), usleep(3C), wait(3C), waitid(2), wait3(3C), waitpid(3C), write(2), writev(2), fcntl(2) (с командой F_SETLKW) • pthread_mutex_lock(3C) не является точкой прерывания pthread_cleanup_push(3C)/pop #include <pthread.h> void pthread_cleanup_push( void (*handler, void *), void *arg); void pthread_cleanup_pop( int execute); pthread_cleanup_push(3C)/pop • Должны использоваться парами в пределах одного блока • В действительности, это макроопределения, содержащие ‘{‘ и ‘}’ • Использование setjmp(3C)/longjmp(3C) в сочетании с pthrhead_cleanup_push/pop приводит к непредсказуемым последствиям – (возможно, к разрушению стека и SIGSEGV) – В лучшем случае – к нарушению порядка вызова обработчиков