PTHREAD_DESTRUCTOR_ITERATIONS
线程退出时,操作系统试图销毁线程特定数据的最大次数PTHREAD_KEYS_MAX
进程可以创建的最大的键的数目PTHREAD_STATC_MIN
一个线程的栈可用的最小字节书目PTHREAD_THREADS_MAX
进程可以创建的最大线程数pthread的属性函数遵循以下的模式:
线程属性为pthread_attr_t
的结构体。可以使用该结构体修改线程的默认属性,并把这些属性与创建线程联系起来。
初始化和销毁函数如下:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
POSIX
的线程属性
* detachstate
线程分离状态属性
* guardsize
线程栈末尾的警戒缓冲区大小属性
* stackaddr
线程栈的最低地址
* stacksize
线程栈的最小长度(字节数)
如果在创建线程时就知道不需要了解线程的终止状态,就可以修改pthread_attr_t
结构中的detachstate
线程属性,让线程一开始处于分离状态。
#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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
detachstate
两个状态:PTHREAD_CREATE_DETACHED
,以分离状态启动线程;PTHREAD_CREATE_JIONABLE
,正常启动线程线程栈管理函数:pthread_attr_getstack
和pthread_attr_setstack
#include <pthread.h>
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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
线程栈大小管理函数:
#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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
guardsize
控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小
#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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
互斥量属性用pthread_mutexattr_t
结构体表示。
初始化和销毁函数:
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
互斥量属性有是3个属性:进程共享属性,健壮属性,类型属性。
PTHREAD_PROCESS_SHARED
进程共享和PTHREAD_PROCESS_PRIVATE
进程不共享#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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
PTHREAD_MUTEX_STALLED
:这意味着持有互斥量的进程终止时不需要采取特别的动作。这种情况下,使用互斥量后的行为是未定义的,等待该互斥量解锁的应用程序会被有效的“拖住”。 PTHREAD_MUTEX_ROBUST
:此时如果线程调用pthread_mutex_lock
获取锁,且该锁被另一个进程持有,但进程终止时并没有对该锁进行解锁,此时线程会阻塞,从pthread_mutex_lock
返回值为EOWNERDEAD
而不是0.当我们检查线程返回值时结果为EOWNERDEAD
。因此我们需要对该互斥量进行恢复。#include <pthread.h>
int pthread_mutexattr_getrobust(
const pthread_mutexattr_t *restrict attr,
int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
如果应用状态无法恢复,在线程对互斥量解锁以后,该互斥量将处于永久不可用状态。为了避免这样的问题,线程可以调用pthread_mutex_consistent
函数,指明与该互斥量相关的状态在互斥量解锁之前是一直的。
#include <pthread.h>
int pthread_mutex_consistent(pthread_mutex_t *mutex);
//返回值:若成功,返回0;否则,返回错误编号
类型属性
函数互斥量类型属性函数:
#include <pthread.h>
int pthread_mutexattr_gettype(
const pthread_mutexattr_t *restrict attr,
int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,
int type);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
初始化和销毁函数
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
唯一属性:进程共享属性
#include <pthread.h>
int pthread_rwlockattr_getpshared(
const pthread_rwlockattr_t *restrict attr,
int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
初始化和销毁
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
进程共享属性和时钟属性
#include <pthread.h>
int pthread_condattr_getpshared(
const pthread_condattr_t *restrict attr,
int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,
int pshared);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
include <pthread.h>
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);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
初始化和销毁:
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
进程共享属性:
#include <pthread.h>
int pthread_barrierattr_getpshared(
const pthread_narrierattr_t *restrict attr,
int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,
int pshared);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
如果一个函数对多线程是可重入的就说这个函数是线程安全的。如果一个函数对于异步处理信号程序是安全的,那么就说函数是异步信号安全的。
提供以线程安全的方式管理FILE对象。FILE对象关联的锁是递归锁
#include <stdio.h>
int ftrylockfile(FILE *fp);
//返回值:若成功,返回0;若不能获取锁,返回非0数值
void flockfile(FILE *fp);
void funlockfile(FILE *fp);
线程特定数据,也称为线程私有数据,是存储和查询某个特定线程相关数据的一种机制。
我们知道一个进程中的所有线程都可以访问这个进程的整个地址空间。除了使用寄存器以外,一个线程没有办法阻止另一个线程访问它的数据。线程特定数据也不例外。虽然底层的实现部分并不能阻止这种访问能力,但管理线程特定数据的函数可以提高线程间的数据独立性,使得线程不太容易访问到其他线程的线程特定数据。
分配线程特定数据之前,需要创建与该数据关联的键。这个键将用于获取对线程特定数据的访问。就是用pthread_key_create
创建一个键。
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp,
void (*destructor)(void *));
//返回值:若成功,返回0;否则,返回错误编号
keyp
指向的内存单元取消键与线程特定数据值之间的关联关系:
#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
//返回值:若成功,返回0;否则,返回错误编号
有些线程可能看到一个键值,而其他的线程可能看到的另一个不同的键值,这取决于系统是如何调度线程的,解决这种竞争的办法是使用pthread_once
:
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
//返回值:若成功,返回0;否则,返回错误编号
如果每个线程都调用pthread_once
,系统就能保证初始化例程initfn
只被调用一次,即系统首次调用时pthread_once
时。创建键时避免出现冲突的一个正确方法如下:
void destructor(void *);
pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;
void thread_init(viod)
{ err = pthread_key_create(&key,destructor); }
int threadfunc(void *arg)
{ pthread_once(&init_done , thread_init); }
键一旦创建以后,就可以通过调用pthread_setspecific
函数吧键和线程特定数据关联起来。可以通过getspecific
函数获得线程特定数据的地址。
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
//返回值:线程特定数据值;若没有值与该键关联,返回NULL
int pthread_setspecific(pthread_key_t key, const void *value);
//返回值:若成功,返回0;否则,返回错误编号
有两个线程属性没有包含在pthread_attr_t
结构中:可取消状态和可取消类型。这两个属性影响着线程在响应pthread_cancel
函数调用时所呈现的行为。
修改取消状态:
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
//返回值:若成功,返回0;否则,返回错误编号
如果应用程序很长一段时间没有调用规定的取消点。那么你可以调用pthread_testcancel
函数在程序中添加自己的取消点。
#include <pthread.h>
void pthread_testcancel(void);
pthread_testcancel
调用就会有任何效果了。我们所描述的默认的取消类型也称为推迟取消。调用pthread_cancel
以后,在线程到达取消点之前,并不会出现真正的取消。也可以调用pthread_setcanceltype
来修改类型:
#include <pthread.h>
int pthread_setcanceltype(int type , int *oldtype);
//返回值:若成功,返回0;否则,返回错误编号
PTHREADCANCEL_DEFERRED
,也可以是PTHREAD_CANCEL_ASYNCHRONOUS
),把原来的取消类型返回到oldtype指向的整型单元。信号的处理是进程中所有线程共享。
在线程中阻止信号发送函数:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset);
//返回值:若成功,返回0;否则,返回错误编号
线程等待一个信号出现:
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
//返回值:若成功,返回0;否则,返回错误编号
发送信号给线程:
#include <signal.h>
int pthread_kill(pthread_t thread,int signo);
//返回值:若成功,返回0;否则,返回错误编号
参考:http://blog.csdn.net/cywosp/article/details/27316803
在程序中fork()与多线程的协作性很差,这是POSIX系列操作系统的历史包袱。因为长期以来程序都是单线程的,fork()运转正常。当20世纪90年代初期引入线程之后,fork()的适用范围就大为缩小了。
在多线程执行的情况下调用fork()函数,仅会将发起调用的线程复制到子进程中。(子进程中该线程的ID与父进程中发起fork()调用的线程ID是一样的,因此,线程ID相同的情况有时我们需要做特殊的处理。)也就是说不能同时创建出于父进程一样多线程的子进程。其他线程均在子进程中立即停止并消失,并且不会为这些线程调用清理函数以及针对线程局部存储变量的析构函数。这将导致下列一些问题:
1. 虽然只将发起fork()调用的线程复制到子进程中,但全局变量的状态以及所有的pthreads对象(如互斥量、条件变量等)都会在子进程中得以保留,这就造成一个危险的局面。例如:一个线程在fork()被调用前锁定了某个互斥量,且对某个全局变量的更新也做到了一半,此时fork()被调用,所有数据及状态被拷贝到子进程中,那么子进程中对该互斥量就无法解锁(因为其并非该互斥量的属主),如果再试图锁定该互斥量就会导致死锁,这是多线程编程中最不愿意看到的情况。同时,全局变量的状态也可能处于不一致的状态,因为对其更新的操作只做到了一半对应的线程就消失了。fork()函数被调用之后,子进程就相当于处于signal handler之中,此时就不能调用线程安全的函数(用锁机制实现安全的函数),除非函数是可重入的,而只能调用异步信号安全(async-signal-safe)的函数。fork()之后,子进程不能调用:
- malloc(3)。因为malloc()在访问全局状态时会加锁。
- 任何可能分配或释放内存的函数,包括new、map::insert()、snprintf() ……
- 任何pthreads函数。你不能用pthread_cond_signal()去通知父进程,只能通过读写pipe(2)来同步。
- printf()系列函数,因为其他线程可能恰好持有stdout/stderr的锁。
- 除了man 7 signal中明确列出的“signal安全”函数之外的任何函数。
2. 因为并未执行清理函数和针对线程局部存储数据的析构函数,所以多线程情况下可能会导致子进程的内存泄露。另外,子进程中的线程可能无法访问(父进程中)由其他线程所创建的线程局部存储变量,因为(子进程)没有任何相应的引用指针。
由于这些问题,推荐在多线程程序中调用fork()的唯一情况是:其后立即调用exec()函数执行另一个程序,彻底隔断子进程与父进程的关系。由新的进程覆盖掉原有的内存,使得子进程中的所有pthreads对象消失。
对于那些必须执行fork(),而其后又无exec()紧随其后的程序来说,pthreads API提供了一种机制:fork()处理函数。利用函数pthread_atfork()来创建fork()处理函数。pthread_atfork()声明如下:
#include <pthread.h>
// Upon successful completion, pthread_atfork() shall return a value of zero; otherwise, an error number shall be returned to indicate the error.
// @prepare 新进程产生之前被调用
// @parent 新进程产生之后在父进程被调用
// @child 新进程产生之后,在子进程被调用
int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));
该函数的作用就是往进程中注册三个函数,以便在不同的阶段调用,有了这三个参数,我们就可以在对应的函数中加入对应的处理功能。同时需要注意的是,每次调用pthread_atfork()函数会将prepare添加到一个函数列表中,创建子进程之前会(按与注册次序相反的顺序)自动执行该函数列表中函数。parent与child也会被添加到一个函数列表中,在fork()返回前,分别在父子进程中自动执行(按注册的顺序)。
使用pread
可以使偏移量的设定和数据的读取成为一个原子操作。