1 线程的基本概念
Linux下的线程在内核是作为共享存储区、共享文件系统、共享信号处理、共享文件描述符,拥有独立进程表项的独立进程看待的,而线程的创建、同步、删除等操作都在核外进行。
编写Linux下的线程程序,需要包含头文件<pthread.h>,链接时需要使用库libpthread.a。
2 线程的使用
2.1 线程的创建
intpthread_create( pthread_t * thread, // 返回创建线程的ID
pthread_attr_t * attr, // 设置线程属性,NULL表示缺省属性
void * (*start_routine)(void *), //线程函数
void * arg) // 传递给线程函数的参数
当线程创建成功时,函数返回0;失败返回-1。
2.2 线程属性
pthread_attr_init(pthread_attr_t * attr)
pthread_attr_destroy(pthread_attr_t *attr )
pthread_attr_get---(const pthread_attr_t *attr, type * ---)
pthread_attr_set---(pthread_attr_t *attr, type ---)
pthread_attr_init( )必须在pthread_create( )之前调用。成功返回0;失败返回-1。
缺省的线程属性如下:
detachstate :PTHREAD_CREATE_JOINABLE
schedpolicy :SCHED_OTHER
priority :0
inherit :PTHREAD_EXPLICIT_SCHED
scope :PTHREAD_SCOPE_PROCESS
stackaddr :NULL,系统分配栈地址
stacksize :0,系统定义栈大小
2.2.1 detachstate ( type = int )
PTHREAD_CREATE_JOINABLE:默认值,其它线程可以通过pthread_join()等待joinable的线程结束,而且joinable的线程只有在其它线程调用pthread_join( )后才释放自己占用的资源。
PTHREAD_CREATE_DETACHED:其它线程不能通过pthread_join()等待detached的线程结束,而且detached的线程结束后马上释放自己占用的资源。
线程分离状态:线程的分离状态决定一个线程以什么样的方式来终止自己。joinable的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用pthread_join( )。需要注意的是,如果一个线程为detached的线程,而且这个线程运行又非常快,它很可能在pthread_create( )返回之前就终止了,它终止以后就可能将线程id和系统资源移交给其他的线程使用,这样pthread_create( )就返回了错误的线程id。要避免这种情况可以采取一定的同步措施,最简单的方法是在被创建的线程里调用pthread_cond_timewait( ),让线程等待一会儿,留出足够的时间让pthread_create( )返回。
intpthread_detach(pthread_t thread)
成功返回0;失败返回-1。
设置joinable的线程为detached,一旦设置为detached,就不能再恢复到joinable。
2.2.2 schedpolicy ( type = int )
SCHED_OTHER :正常、非实时(默认值)
SCHED_RR :实时、轮转法
SCHED_FIFO :实时、先入先出
SCHED_RR和SCHED_FIFO只能用于具有root权限的线程。
2.2.3 schedparam ( type = structsched_param )
schedparam仅当schedpolicy为实时(SCHED_RR或SCHED_FIFO)才有效。
sched_param只有有一个整型变量sched_priority,表示线程的运行优先级,默认值为0。
2.2.4 inheritsched ( type = int )
PTHREAD_EXPLICIT_SCHED :默认值,新线程使用指定的schedpolicy和schedparam。
PTHREAD_INHERIT_SCHED :新创建的线程使用父线程的schedpolicy和schedparam。
2.2.5 scope ( type = int )
PTHREAD_SCOPE_PROCESS :默认值,同一进程中的线程相互竞争,Linux不支持。
PTHREAD_SCOPE_SYSTEM :与系统中的所有进程一起竞争,Linux支持。
关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:LightWeight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,顾名思义,即某个线程固定的绑在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。
PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
2.3 线程终止
一般来说,线程终止有两种情况:
正常终止:线程调用pthread_exit( ),或者线程函数执行return语句。二者完成的任务是相同的,return语句会自动调用pthread_exit()。
非正常终止:线程收到其它线程发来的取消请求而终止,相当于在cancelation-point调用pthread_exit( PTHREAD_CANCELED )。
2.3.1 正常终止
voidpthread_exit( void *retval )
retval是线程终止的返回值,由用户指定;线程终止的返回值可以由pthread_join( )得到。
intpthread_join( pthread_t thread, void **thread_return )
pthread_join( )挂起当前线程,直到指定的线程终止。成功返回0;失败返回-1。
如果线程正常终止,*thread_return = retval;
如果线程非正常终止,*thread_return = PTHREAD_CANCELED。
注意:一个joinable的线程只允许有唯一一个线程使用pthread_join( )等待它终止。
2.3.2 非正常终止
intpthread_cancel( pthread_t thread )
发送终止信号给指定的线程,成功返回0;失败返回-1。
发送成功并不意味着指定的线程会终止。
intpthread_setcancelstate( int state, int *oldstate )
成功返回0;失败返回-1。state可以取:
PTHREAD_CANCEL_ENABLE :缺省值,线程收到cancel信号后立即终止。
PTHREAD_CANCEL_DISABLE:忽略cancel信号。
intpthread_setcanceltype( int type, int *oldtype )
成功返回0;失败返回-1。仅当cancelstate为ENABLE时有效,type可以取:
PTHREAD_CANCEL_DEFFERED :默认值,收到cancel信号后继续执行到下一个
cancelation-point后终止。
PTHREAD_CANCEL_ASYCHRONOUS :收到cancel信号后立即终止。
voidpthread_testcancel( void )
检查线程是否已经收到cancel信号,如果已经收到,立即终止本线程;否则立即返回。
2.3.3 线程终止后的清理
voidpthread_cleanup_push( void (*routine) (void *), void *arg )
voidpthread_cleanup_pop( int execute )
execute表示是否在弹出清理函数的同时执行该清理函数,0表示不执行,非0表示执行。
注意:两个函数必须成对出现,而且必须出现在同一级别的{ }内;能够导致跳离该函数对的return,break,continue,goto语句不能出现在函数对中。
实际例子:
pthread_cleanup_push( pthread_mutex_unlock,(void *) &mut );
pthread_mutex_lock( &mut );
……; // 可能会使线程终止的语句
pthread_mutex_unlock( &mut );
pthread_cleanup_pop( 0 );
最后的两条语句可以用一条语句代替:
pthread_cleanup_pop( 1 );
注意:上面的例子只有在线程取消状态是PTHREAD_CANCEL_DEFERRED时才安全。当消状态是PTHREAD_CANCEL_ASYCHRONOUS时,如果取消事件发生在pthread_cleanup_push( )和pthread_mutex_lock()之间,或者发生在pthread_mutex_unlock( )和pthread_cleanup_pop( )之间,线程就会unlock一个unlocked的mutex。所以需要使用:
pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED,&oldtype );
pthread_cleanup_push( pthread_mutex_unlock,(void *) &mut );
pthread_mutex_lock( &mut );
……;
pthread_cleanup_pop( 1 );
pthread_setcanceltype( oldtype, NULL );
2.4 线程相关函数
获得本线程ID :pthread_tpthread_self( void )
判断是否为同一线程 :intpthread_equal( pthread_t thread1, pthread_t thread2 )
// 参数once_control的值必须为PTHREAD_ONCE_INIT。
pthread_once_tonce_control = PTHREAD_ONCE_INIT;
intpthread_once( pthread_once_t *once_control, void (*init_routine) (void) )
不管当前进程中的各个线程调用pthread_once( )多少次,init_routine()仅执行一次;而且只有当init_routine( )运行结束后,pthread_once( )才能返回。
成功返回0;失败返回-1。
3 线程数据TSD(Thread-SpecificData)
在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据TSD(Thread-SpecificData)。在线程内部,各个函数可以象使用全局变量一样使用它,但它对线程外的其它线程是不可见的。例如变量errno,它返回标准的出错信息,显然不能是一个局部变量,几乎每个函数都可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,就必须使用线程数据。
我们为每个线程数据创建一个键,把线程数据和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。和线程数据相关的函数主要有4个:
1.创建一个键
intpthread_key_create( pthread_key_t *key, void (*destr_function) (void *) )
线程终止时,将用与key相关联的数据做为参数调用destr_function( )。
不论哪个线程调用pthread_key_create( ),所创建的key是所有线程都可以访问的,但是各个线程可以根据自己的需要与key关联不同的数据,这就相当创建了一个同名而不同值的全局变量。
为了只创建一次线程键,pthread_key_create( )常和pthread_once()一起使用。
2.删除一个键
intpthread_key_delete( pthread_key_t key )
pthread_key_delete( )并不检查是否有线程还在使用key,也不会调用destr_function( ),而只是将key释放以供下一次pthread_key_create( )使用。
3.为一个键指定线程数据
4.从一个键读取线程数据
intpthread_setspecific( pthread_key_t key, const void *value )
void* pthread_getspecific( pthread_key_t key )
4 线程同步
4.1 Mutex( include <pthread.h>)
4.1.1 Mutex的创建和注销
静态方式初始化mutex:
pthread_mutex_tmutex = PTHREAD_MUTEX_INITIALIZER;
动态方式初始化mutex:
intpthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t*mutexattr )
如果参数mutexattr为NULL,表示使用默认属性。
注销mutex:
intpthread_mutex_destroy( pthread_mutex_t *mutex )
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此Linux中的pthread_mutex_destroy( )除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
4.1.2 Mutex的属性
缺省的线程属性如下:
protocol PTHREAD_PRIO_NONE
pshared PTHREAD_PROCESS_PRIVATE
type PTHREAD_MUTEX_DEFAULT
pthread_mutexattr_init( )必须在pthread_mutex_init( )之前调用:
intpthread_mutexattr_init( pthread_mutexattr_t * mutexattr )
intpthread_mutexattr_destroy( pthread_mutexattr_t *mutexattr )
intpthread_mutexattr_get--( const pthread_mutexattr_t * mutexattr, int * value )
intpthread_mutexattr_set--( pthread_mutexattr_t * mutexattr, int value )
1.prioceiling:
互斥锁的优先级上限。优先级上限指定了能够执行互斥锁保护的临界代码段的最低优先级。为避免优先级倒置,请将prioceiling设置为高于或等于可能会锁定互斥锁的所有线程的最高优先级。
2.protocol:
PTHREAD_PRIO_NONE
线程的优先级和调度不受互斥锁拥有权的影响。
PTHREAD_PRIO_INHERIT
如果更高优先级的线程因线程A拥有的一个或多个互斥锁而被阻塞,而这些互斥锁是用PTHREAD_PRIO_INHERIT初始化的,则线程A将以高于它的优先级或者所有正在等待这些互斥锁的线程的最高优先级运行。
如果线程A因另一个线程B拥有的互斥锁而被阻塞,则相同的优先级继承效应会以递归方式传播给线程B。
使用PTHREAD_PRIO_INHERIT可以避免优先级倒置。低优先级的线程持有高优先级线程所需的锁时,便会发生优先级倒置。只有在低优先级的线程释放该锁之后,高优先级的线程才能继续使用该锁。
PTHREAD_PRIO_PROTECT
当线程拥有一个或多个PTHREAD_PRIO_PROTECT初始化的互斥锁时,会影响其他线程(如线程A)的优先级和调度。线程A会以其较高的优先级或者以线程A拥有的所有互斥锁的最高优先级上限运行。基于被 thrd2 拥有的任一互斥锁阻塞的较高优先级线程对于 thrd2 的调度没有任何影响。
一个线程可以同时拥有多个使用PTHREAD_PRIO_INHERIT和PTHREAD_PRIO_PROTECT初始化的互斥锁。在这种情况下,该线程将以通过其中任一获取的最高优先级执行。
3.pshared
PTHREAD_PROCESS_PRIVATE 进程内专用锁
PTHREAD_PROCESS_SHARED 系统范围内的锁
4.type
PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_ERRORCHECK
| DEFAULT | NORMAL | RECURSIVE | ERRORCHECK |
lock unlock的mutex | √ | √ | √ | √ |
lock被其它线程lock的mutex | √ | √ | √ | √ |
lock被本线程lock的mutex | un-defined | dead-lock | 成功lock返回 | 失败返回错误 |
unlock unlock的mutex | un-defined | un-defined | 失败返回错误 | 失败返回错误 |
unlock被其它线程lock的mutex | un-defined | un-defined | 失败返回错误 | 失败返回错误 |
unlock被本线程lock的mutex | √ | √ | unlock一次 | √ |
4.1.3 Mutex的操作
intpthread_mutex_lock( pthread_mutex_t *mutex )
intpthread_mutex_unlock( pthread_mutex_t *mutex )
intpthread_mutex_trylock( pthread_mutex_t *mutex )
pthread_mutex_trylock( )是pthread_mutex_lock( )的非阻塞版本,如果mutex参数指定的互斥锁已经被锁定,pthread_mutex_trylock( )不会阻塞当前线程,而是立即返回一个描述互斥锁状况的值。
4.2 条件变量( include<pthread.h>)
条件变量主要包括两个动作:一个线程等待条件成立而挂起并等待;另一个线程使条件成立(给出条件成立信号)。条件变量必须和一个互斥锁一起使用,用来避免其它线程在本线程开始等待条件成立之前就给出条件成立的信号。
4.2.1 条件变量的创建和注销
静态初始化:
pthread_cond_tcond = PTHREAD_COND_INITIALIZER
动态初始化:
intpthread_cond_init( pthread_cond_t *cond, pthread_condattr_t *cond_attr )
如果参数cond_attr为NULL,表示使用默认属性。
注销条件变量:
intpthread_cond_destroy( pthread_cond_t *cond )
4.2.2 条件变量的等待
intpthread_cond_wait ( pthread_cond_t *cond, pthread_mutex_t*mutex )
intpthread_cond_timedwait( pthread_cond_t*cond, pthread_mutex_t *mutex,
const struct timespec *abstime )
如果pthread_cond_timedwait( )在参数abstime指定的时刻前没有收到条件成立信号,线程结束等待,并失败返回。绝对时间的设定:
struct timespec now, timeout; // seconds +nano-secondes, #include <time.h>
clock_gettime( CLOCK_REALTIME, &now );
timeout.tv_sec = now.tv_sec + 5;
timeout.tv_nsec = now.tv_nsec;
mutex必须静态初始化为PTHREAD_MUTEX_INITIALIZER,而且在调用pthread_cond_wait( )之前必须由当前线程lock该mutex,也就是说当前线程必须占有该mutex:
1.当线程调用pthread_cond_wait( ),开始等待之前,mutex自动被unlock;
2.当线程收到条件成立信号,离开pthread_cond_wait( )之前,mutex自动被重新lock。
等待信号的一般步骤:
{
pthread_mutex_lock( &mut );// 尽量早的lock,就可以更早的防止signal发出而未被发现
// ……
pthread_cond_wait( &cond,&mut );
pthread_mutex_unlock( &mut );// 尽量快的unlock,使发送信号的地方尽快的获得mutex
}
发送信号的一般步骤:
{
pthread_mutex_lock( &mut );// 尽量晚的lock,只要在signal发送前lock就可以
pthread_cond_signal( &cond );
pthread_mutex_unlock( &mut );// 尽量快的unlock,这样等待信号的地方才能够更快的响应
}
4.2.3 条件变量的激发
intpthread_cond_signal( pthread_cond_t *cond )
intpthread_cond_broadcast( pthread_cond_t *cond )
pthread_cond_signal( )激活一个等待该条件变量的线程;而pthread_cond_broadcast( )则激活所有等待该条件变量的线程。
4.3 信号(include < pthread.h> include < signal.h> )
4.3.1 信号的设置
intpthread_sigmask( int how, const sigset_t * newmask, sigset_t *oldmask )
参数how改变当前阻塞信号的集合:
SIG_BLOCK :在当前线程的阻塞信号集中添加set中的信号。
SIG_UNBLOCK:在当前线程的阻塞信号集中删除set中的信号。
SIG_SETMASK:更新当前线程的阻塞信号集为set。
intsigfillset( sigset_t *set ) // set中将包含Linux支持的64种信号
intsigemptyset( sigset_t *set )
intsigaddset( sigset_t *set, int signum )
intsigdelset( sigset_t *set, int signum )
intsigismember( const sigset_t *set, int signum ) // 存在返回1,不存在返回0,失败返回-1。
4.3.2 信号的发送
intpthread_kill( pthread_t thread, int signum )
4.3.3 信号的等待
4.3.3.1 sigwait( )
int sigwait( const sigset_t *set, int *signum )
挂起当前线程,直到set中的一个信号到达,并将该信号从线程的pending list中清除。
如果set中的某个信号已经pending,则立即返回,并将该信号从线程的pending list中清除。
成功返回0,失败返回-1。
4.3.3.2 sigtimedwait( )
intsigtimedwait( const sigset_t *set, siginfo_t *info, const struct timespec*timeout );
如果timeout为0,立即返回。
成功返回信号值,失败返回-1。
4.3.3.3 sigwaitinfo( )
intsigwaitinfo( const sigset_t *set, siginfo_t *info )
成功返回信号值,失败返回-1。