12.3线程属性
#include <pthread.h>
int pthread_attr_init(pthread-attr_t *attr)
int pthread_attr_destroy(pthread_attr_t * attr)
可以使用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以使用int pthread_attr_init(pthread-attr_t *attr)函数初始化,pthread_attr_t就包含的内容就是操作系统实现支持的线程所有属性的默认值。
int pthread_attr_destroy(pthread_attr_t * attr)用来去除pthread_attr_t的初始化,释放分配的动态内存空间。
#include<pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t * restrict attr, int *detachstate)
int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate)
用于获取设置detachstate属性。
线程栈
#include<pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrick stackadd,,size_t *restict stacksize)
int pthread_attr_setstack(const pthread_attr_t *attr, void *stackaddr, size_t *stacksize)
如果应用程序使用了太多的线程,致使线程栈的累计大小超过了可用的虚拟地址空间,这时就需要减少线程的默认的栈大小,另一方面,如果线程调用的的函数分配了大量的自动变量或者调用的函数涉及很深的栈帧,那么这时需要的栈大小可能要比默认的大。
如果用完了线程栈的虚拟地址空间,可以使用malloc或者mmap来为其它栈分配空间,并用pthread_attr_setstack函数来改变新建线程的栈位置。线程栈所占内存范围中可寻址的最低地址可以由stackaddr参数指定,该地址与处理器结构相应的边界对齐。
stackadd可能是栈的最低地址或是结尾地址。
#include<pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize)
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
只用来分配大小,不管理线程栈得分配问题。
#include<pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize)
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize)
线程属性guardsize控制着线程栈末尾之后用来以避免栈溢出的扩展内存的大小。
#int <pthread.h>
int pthread_getconcurrency(void)
int pthread_serconcurrency(int level)
并发度控制着用户级线程可以映射的内核线程或进程的数目。
----------------------------------------------------------------------
12.4同步属性
1互斥量属性
#include<pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr)
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
进程中,多个线程可以访问同一个同步对象。
允许相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间中。PTHREAD_RPOCESS_SHARED
#include<pthread.h>
int pthread_mutexattr_getpshared(const pthread_mutexattr_t * restrict attr, int *restrict pshared)
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared)
----------------------------------------------------------------------
12.5重入
支持线程安全函数的操作系统实现会在<unistd.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS。
如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是异步-信号安全的。
以线程安全的方式管理FILE对象的方法。
#include,stdio.h>
int ftrylockfile(FILE*fp)
void flockfile(FILE *fp)
void funlockfile(FILE*fp)
---------------------------------------------------
12.6线程私有数据
线程私有数据是存储和查询与某个线程相关的数据的一种机制。
1)有时候需要维护基于每个线程的数据。
2)它提供了让基于进程的接口适应多线程环境的机制。
在分配线程私有数据之前,需要创建与该数据关联的键。这个键将用于获取对线程私有数据的访问权。
#include<pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void*))
创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线成使用,但每个线程把这个键与不同的线程私有数据地址进行关联。创建新键时,每个线程的数据地址设为null值。
还可以关联析构函数,当线程退出时,如果数据地址已经被设置为非null数值,那么析构函数就会被调用,它唯一的参数就是该数据地址。当线程执行返回时,会调用析构函数。如果exit,_Exit,abort就不会调用析构函数。
如果线程没有释放内存就退出了,那么这块内存将会丢失,即线程所属进程出现了内存泄漏。
线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。每个操作系统在实现的时候可以对进程可分配的键数量进行限制。
线程退出时,线程私有数据的析构函数将按照操作系统实现中定义的顺序被调用。析构函数可能会调用另一个函数,该函数可能会创建新的线程私有数据而且把这个数据与当前的键关联起来。当所有的析构函数都调用完成之后,系统会检查是否还有非null的线程私有数据值与键关联,如果有的话,再次调用析构函数。这个过程将会一直重复直到线程所有的键都为null值线程私有数据,或者已经做了PTHREAD_DESTRUCTOR_ITERATIONS中定义的最大次数的尝试。
#include<pthread.h>
int pthread_key_delete(pthread_key_t *key)
用来取消键与线程私有数据值之间的关联关系。
#include<pthread.h>
int pthread_key_delete(pthread_key_t *key)
此操作不会激活与键关联的析构函数。要释放任何与键对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。
#include<pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT
int pthread_once(pthread_once_t *initflag, void (*initfn)(void))
每个线程都调用pthread_once,系统就能保证初始化例程initfn只被调用一次,即在系统首次调用pthread_once时。
#include<pthread.h>
void *pthread_getspecific(pthread_key_t key)
int pthread_setspecific(pthread_key_t key, const void *value)
用来把键和线程私有数据关联起来。
-----------------------------------------------------------
12.7取消选项
可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCEL_DISABLE
#include,pthread.h>
int pthread_setcancelstate(int state,int *oldstate);
在默认情况下,线程在取消请求发出以后还是继续运行,直到线程到达某个取消点。当PTHREAD_CANCEL_DISABLE时,对pthread_cancel的调用并不会杀死线程,相反,取消请求对这个线程来说处于未决状态。当变为PTHREAD_CANCEL_ENABLE时,线程将在下一个取消点上对所有未决的取消请求进行处理。
void pthread_testcancel(void)
自己添加取消点。
int pthread_setcanceltype(int type, int *oldtype)
用来修改取消类型。
--------------------------------------------------------------------
12.8线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。
进程中的信号是递送到单个线程的。如果信号与硬件故障或计时器超时相关,该信号就被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。
int pthread-sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)
用来阻止信号发送。同sigprocmask基本相同。
int sigwait(const sigset_t *restrict set, int *restrict signop)
等待一个或多个信号发生。
int pthread_kill(pthread_ thread, int signo)
把信号发送到线程。
-----------------------------------------------
12.9线程和fork
在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中线程占有锁,子进程同样占有这些锁,问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法直到它占有了那些锁并且需要释放那些锁。
如果fork后,马上调用exec就可以避免此问题。老的地址空间被丢弃,所以锁的状态无关紧要。
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
清除锁状态。
prepare由父进程在fork创建子进程前调用,获取父进程定义的所有锁。parent 是在fork创建子进程之后,但是在fork返回值前在父进程环境中调用,对prepare获得的所有锁进行解锁。child在fork返回之前在子进程环境中调用,同parent一样。