线程控制
线程限制
PTHREAD_DESTRUCTOR_ITERATIONS 线程退出系统时试图销毁线程特定数据最大次数 _SC_THREAD_DESTRUCTOR_ITERATIONS
PTHREAD_KEYS_MAX 进程可创建键的最大次数 _SC_THREAD_KEYS_MAX
PTHREAD_STACK_MIN 一个线程的栈可用的最小字节数 _SC_THREAD_STACK_MIN
PTHREAD_THREADS_MAX 进程可创建的最大线程数 _SC_THREAD_THREADS_MAX
线程属性
(1). 每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,…)。属性对象对应用程序不透明,有相应的函数来管理这些属性对象。
(2). 有一个初始化函数,把属性设置为默认值。
(3). 有一个销毁属性对象的函数。如初始化函数分配了与属性对象关联的资源,销毁函数负责释放这些资源。
(4). 每个属性有一个从属性对象中获取属性值的函数。成功,返回0。失败,返回错误编号。
(5). 每个属性有一个设置属性值的函数。属性值按值传递。
detachstate
,线程的分离状态属性.
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int* detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate);
如果对现有的某个线程的终止状态不感兴趣的话,可使用pthread_detach
让操作系统在线程退出时收回它所占用的资源。如在创建线程时不需要了解线程终止状态,可修改pthread_attr_t
的detachstate
属性,让线程一开始就处于分离状态。
可用pthread_attr_setdetachstate
,把线程属性detachstate
设为PTHREAD_CREATE_DETACHED
,以分离启动线程。或PTHREAD_CREATE_JOINABLE
正常启动线程,应用程序可获取线程终止状态。
guardsize
,线程栈末尾的警戒缓存区大小(字节数).
stackaddr
,线程栈的最低地址.
stacksize
,线程栈的最小长度(字节数).
对遵循POSIX
标准的系统,不一定要支持线程栈属性。对遵循XSI
选项的系统,需要支持。可在编译阶段,用_POSIX_THREAD_ATTR_STACKADDR
和_POSIX_THREAD_ATTR_STACKSIZE
来检查系统是否支持每个线程栈属性。也可在运行阶段把_SC_THREAD_ATTR_STACKADDR
和_SC_THREAD_ATTR_STACKSIZE
传给sysconf
。
可用pthread_attr_getstack
和pthread_attr_setstack
对线程栈属性进行管理。
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
void **restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
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);
线程属性guardsize
控制着线程栈末尾后,用以避免栈溢出的扩展内存的大小。
属性默认值由具体实现定义。常用值是系统页大小。如果修改了线程属性stackaddr
,系统就认为我们将自己管理栈,使警戒缓冲区机制无效。等同于把guardsize
设为0
。
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);
如果线程的栈指针溢出到警戒区域,应用程序可能通过信号接收出错信息。
同步属性
(1). 互斥量属性
互斥量属性用pthread_mutexattr_t
结构表示。
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
值得注意的3
个属性是:进程共享属性,健壮属性,类型属性。
PTHREAD_PROCESS_SHARED
,从多进程共享的内存块中分配的互斥量可用于共享进程间的同步。
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
互斥量健壮属性与在多个进程间共享的互斥量有关。持有互斥量进程终止时,需要解决互斥量状态恢复问题。
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);
健壮属性默认值为PTHREAD_MUTEX_STALLED
,意味着持有互斥量的进程终止时不需要采取特别的动作。
这时,使用互斥量后行为未定义。等待该互斥量的应用会被拖住。
另一个取值是PTHREAD_MUTEX_ROBUST
,将导致后续其他等待此锁线程阻塞。
使用健壮的互斥量,需检查pthread_mutex_lock
的3
个返回值:
不需要恢复的成功,需要恢复的成功,失败。
如应用状态无法恢复,线程对互斥量解锁后,互斥量永久不可用。为避免,可调用pthread_mutex_consistent
,指明与该互斥量相关的状态在互斥量解锁前是一致的。
int pthread_mutex_consistent(pthread_mutex_t *mutex);
如线程没有先调用pthread_mutex_consistent
就对互斥量解锁,则其他试图获取该互斥量线程得到错误码ENOTRECOVERABLE
。
类型互斥量属性控制着互斥量的锁定特性。
POSIX.1
定义了4
中类型。
PTHREAD_MUTEX_NORMAL
,不做特殊的错误检查或死锁检测。
PTHREAD_MUTEX_ERRORCHECK
,提供错误检查
PTHREAD_MUTEX_RECURSIVE
,允许同一线程在互斥量解锁前对该互斥量多次加锁。递归互斥量维护锁的计数。在解锁次数和加锁次数不相同下,不会释放锁。
PTHREAD_MUTEX_DEFAULT
,由系统将其映射为上述三者的一种
互斥量类型 | 没解锁时重新加锁 | 不占有时解锁 | 在已解锁时解锁 |
---|---|---|---|
PTHREAD_MUTEX_NORMAL | 死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFAULT | 未定义 | 未定义 | 未定义 |
int pthread_mutexattr_gettype(const pthread_mutexattr_t* restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
(2). 读写锁属性
用pthread_rwlockattr_init
初始化pthread_rwlockattr_t
结构。用pthread_rwlockattr_destroy
销毁。
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t* restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
(3). 条件变量属性
进程共享属性,时钟属性
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
重入
多个控制线程在相同的时间有可能调用相同的函数。如果一个函数在相同的时间点可被多个线程安全地调用,称该函数是线程安全的。支持线程安全函数的系统会在<unist.h>
中定义符号_POSIX_THREAD_SAFE_FUNCTIONS
。应用也可在sysconf
中传入_SC_THREAD_SAFE_FUNCTIONS
。
如一个函数对多个线程来说是可重入的,称其是线程安全的。如果函数对异步信号处理程序的是安全的,就说函数是异步信号安全的。以线程安全方式管理FILE
对象。可用flockfile,ftrylockfile
获取给定FILE
对象关联的锁。此锁是递归的。
int ftrylockfile(FILE* fp);
void flockfile(FILE* fp);
void funlockfile(FILE* fp);
int getchar_unlocked(void);
int getc_unlocked(FILE* fp);
// 成功,返回c。出错,返回EOF。
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
线程特定数据
在分配线程特定数据前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void*));
创建的键存储在keyp指向的内存单元中,可被进程的所有线程使用。但每个线程把这个键与不同线程特定数据地址关联。
创建新键时,每个线程的数据地址为空值。线程退出时,如数据地址已被置为非空值,则析构函数会被调用,唯一参数为数据地址。
线程调pthread_exit
或线程返回,正常退出,析构函数被调用。
线程取消时,在最后的清理处理程序返回后,析构函数被调用。
如线程调了exit, _exit, _Exit
或abort
或其他非正常退出,不调用析构。
线程退出时,线程特定数据的析构函数按系统中定义的顺序调用。
所有析构函数完成后,检查是否还有非空的线程特定数据值与键关联,如有,再次析构。可通过pthread_key_delete
取消键和线程特定数据值间的关系。
int pthread_key_delete(pthread_key_t key);
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,// 需指向一个全局或静态变量,初始化为PTHREAD_ONCE_INIT
void (*initfn)(void));
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void* value);
在多线程环境中,有些事仅需要执行一次。通常当初始化应用程序时,可以比较容易地将其放在main
函数中。
但当你写一个库时,就不能在main
里面初始化了,你可以用静态初始化,但使用一次初始化(pthread_once
)会比较容易些。
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
功能:
本函数使用初值为PTHREAD_ONCE_INIT
的once_control
变量,保证init_routine()
函数在本进程执行序列中仅执行一次。
在多线程编程环境下,尽管pthread_once()
调用会出现在多个线程中,init_routine()
函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。
取消选项
可取消状态,可取消类型。可取消状态属性可以是PTHREAD_CANCEL_ENABLE
,也可是PTHREAD_CANCEL_DISABLE
。
int pthread_setcancelstate(int state, int *oldstate);
pthread_cancel
不等待线程终止。线程在取消请求发出后,还是继续运行,直到到达某个取消点。
线程启动时默认可取消状态为PTHREAD_CANCEL_ENABLE
。线程在到达取消点,检查是否有取消请求并处理。
void pthread_testcancel(void);
int pthread_setcanceltype(int type, int* oldtype);
线程和信号
每个线程都有自己的信号屏蔽字。但信号的处理所有线程共享。
进程中的信号被递送到单个线程。有些信号被发送到任意一个线程。
sigprocmask
的行为在多线程的进程中没定义。此时,线程用pthread_sigmask
。
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
int sigwait(const sigset_t *restrict set,// 等待的信号集
int *restrict signop);// 信号数量
int pthread_kill(pthread_t thread, int signo);
闹钟是进程资源。
线程和fork
线程调用fork
时,子进程通过继承整个地址空间的副本。
从父进程继承了每个互斥量,读写锁,条件变量。
如果父进程包含一个以上线程,子进程fork
后,如果不是马上调exec
,就需要清理锁的状态。
在子进程内部,只存在一个线程,由父进程中调用fork
的线程副本构成。
父进程中的线程如占有锁,子进程将同样占有这些锁。但子进程不包含占有锁的线程的副本。故,子进程不知道占有了哪些锁,释放哪些锁。在多线程的进程中,为避免不一致状态,
POSIX.1
声明,fork
返回和子进程调用其中一个exec
间,子进程只能调用异步信号安全的函数。要清除锁状态,可以用pthread_atfork
建立fork
.
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
最多可安装3
个帮助清理锁的函数。prepare
在由父进程在fork
创建子进程前调用,用于获取父进程定义的所有锁。parent
在fork
创建子进程后,返回前,在父进程上下文调用,用于对prepare
获取的所有锁进行解锁。child
在fork
返回前在子进程上下文调用。用于对prepare
获取的所有锁进行解锁。
好像出现了下列事件序列:
父进程获取所有的锁,子进程获取所有的锁,父进程释放它的锁,子进程释放它的锁。
可多次调用pthread_atfork
来设置多套fork
处理程序。使用了多个fork
处理程序时,parent,child
处理程序按注册顺序调用。prepare
按注册时相反顺序调用。
如:模块A
调模块B
的函数,且每个模块有自己一套锁。如锁的层次是,A
在B
之前,模块B
需在模块A
之前设置它的fork
处理程序。父进程调用fork
时会执行以下步骤,假设子进程在父进程之前运行。
a. 调模块A
的prepare
b. 调模块B
的prepare
c. 创建子进程
d. 调模块B
的child
e. 调模块A
的child
f. fork
返回到子进程
g. 调模块B
的parent
h. 调模块A
的parent
i. fork
返回父进程
线程和I/O
pread
和pwrite
可以使设置文件位置和读取或写入整合为原子操作,适合多线程。