看了很多关于linux下面多线程开发的博客和书籍,最后还是觉得杨沙洲在IBM developerWorks 中国发表的关于Posix线程编程的专栏差不多是最好的吧,本文就以该专栏来一步一步来学习多线程开发。
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg)使用多线程时候,必须包含头文件pthread.h。并且在编译过程中需要使用-lpthread来链接线程库。第一个参数用来返回创建的线程的线程标识符。第二个参数为线程属性字段,一般设为NULL来使用默认值。第三个为线程的执行体,参数为void 指针,返回的也是void 指针。最后参数用来传递参数给指向体的参数。函数调用成功返回0,失败了,返回错误代码。
#include <pthread.h> void pthread_exit(void *retval); int pthread_join(pthread_t thread, void **retval);
在线程内部通过pthread_exit终止线程的执行,并返回一个指向某个对象的指针(注意:绝对不能用它来返回一个指向局部变量的指针)。在“父线程”中通过pthread_join函数阻塞等待"子线程"执行结束,并且通过retval指针指向进程退出时候返回的对象的指针,执行成功时返回0,错误返回错误代码。
非正常终止指的是在线程外部通过向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定,缺省处理是继续运行至取消点。
#include <pthread.h> int pthread_cancel(pthread_t thread) //发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。 int pthread_setcancelstate(int state, int *oldstate) //设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。 int pthread_setcanceltype(int type, int *oldtype) //设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。 void pthread_testcancel(void) //检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。
#include <pthread.h> int pthread_attr_init(pthread_attr_t *attr);//对象初始化 int pthread_attr_destroy(pthread_attr_t *attr);//对象清理和回收初始化一个线程对象以后,可以通过一系列的getter/setter进行操作该对象,拿脱离状态为例子格式如下:
#include <pthread.h> int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置属性 int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);//获得属性下面介绍个常见的属性:
#include <pthread.h> int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));//创建 int pthread_key_delete(pthread_key_t key);//销毁 void *pthread_getspecific(pthread_key_t key);//从key中获得相关联的指针 int pthread_setspecific(pthread_key_t key, const void *value);//设置key相关联的指针
下面是原文的一个简单例子:
#include <stdio.h> #include <pthread.h> pthread_key_t key; void echomsg(int t) { printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t); } void * child1(void *arg) { int tid=pthread_self(); printf("thread %d enter\n",tid); pthread_setspecific(key,(void *)tid); sleep(2); printf("thread %d returns %d\n",tid,pthread_getspecific(key)); sleep(5); } void * child2(void *arg) { int tid=pthread_self(); printf("thread %d enter\n",tid); pthread_setspecific(key,(void *)tid); sleep(1); printf("thread %d returns %d\n",tid,pthread_getspecific(key)); sleep(5); } int main(void) { int tid1,tid2; printf("hello\n"); pthread_key_create(&key,echomsg); pthread_create(&tid1,NULL,child1,NULL); pthread_create(&tid2,NULL,child2,NULL); sleep(10); pthread_key_delete(key); printf("main thread exit\n"); return 0; } //给例程创建两个线程分别设置同一个线程私有数据为自己的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果可以看出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid。
#include <pthread.h> //锁创建和初始化 int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //删除锁 int pthread_mutex_destroy(pthread_mutex_t *mutex);从上面我们看到有两种方式来创建和初始化一个pthread_mutex_t类型的互斥锁:利用pthread_mutex_init()函数和利用结构常量PTHREAD_MUTEX_INITIALIZER。
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
#include <pthread.h> //创建初始化 int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //注销 int pthread_cond_destroy(pthread_cond_t *cond);和互斥锁一样,对条件变量的初始化也有两种方式:利用pthread_cond_init函数和常量PTHREAD_COND_INITIALIZER。尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
#include <pthread.h> //等待 int pthread_cond_timedwait(pthread_cond_t *restrictcond,pthread_mutex_t *restrictmutex,const struct timespec *restrictabstime); int pthread_cond_wait(pthread_cond_t *restrictcond,pthread_mutex_t *restrictmutex); //激活 int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()的竞争条件。mutex互斥锁必须是普通锁或者适应锁,且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
这是博文的原话,不是很清楚,这样解释:
pthread_cond_wait是一个阻塞函数,函数传入的互斥量mutex是用于保护条件。因为我们在调用pthread_cond_wait时,如果条件不成立我们就进入阻塞,但是进入阻塞这个期间,如果条件变量改变了的话,那我们就漏掉了这个条件。因为这个线程还没有放到等待队列上,所以调用pthread_cond_wait前要先锁互斥量,即调用pthread_mutex_lock(),pthread_cond_wait在把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获得加锁的权利。这样其它线程才能对临界资源进行访问并在适当的时候唤醒这个阻塞的进。当pthread_cond_wait返回的时候又自动给mutex加锁。pthread_cond_wait()的使用方法如下:
pthread_mutex_lock(&qlock); /*lock*/ pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait() return-->lock*/ pthread_mutex_unlock(&qlock); /*unlock*/
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。使用的方法:
pthread_mutex_lock(&qlock); /*lock*/ pthread_cond_signal(&qready); pthread_mutex_unlock(&qlock); /*unlock*/
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem);POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了总是可用于多进程之间以外,在使用上与无名灯并没有很大的区别,因此下面仅就无名灯进行讨论。
sem_destroy()被注销的信号灯sem要求已没有线程在等待该信号灯,否则返回-1,且置errno为EBUSY。除此之外,LinuxThreads的信号灯注销函数不做其他动作。
#include <semaphore.h> //点灯 int sem_post(sem_t *sem); //灭灯 int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //获得灯的值 int sem_getvalue(sem_t * sem, int * sval)sem_post()点灯操作将信号灯值原子地加1,表示增加一个可访问的资源。
sem_wait()为等待灯亮操作,等待灯亮(信号灯值大于0),然后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,如果信号灯计数大于0,则原子地减1并返回0,否则立即返回-1,errno置为EAGAIN。sem_timedwait加了等待时间。
sem_getvalue()读取sem中的灯计数,存于*sval中,并返回0。
注意:sem_wait()被实现为取消点。
#include <pthread.h> void pthread_cleanup_push(void (*routine)(void *),void *arg);// void pthread_cleanup_pop(int execute);pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut); pthread_mutex_lock(&mut); /* do some work */ pthread_mutex_unlock(&mut); pthread_cleanup_pop(0);必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np() / pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:
{ int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(routine, arg); ... pthread_cleanup_pop(execute); pthread_setcanceltype(oldtype, NULL); }
#include <pthread.h> pthread_t pthread_self(void);本函数返回本线程的标识符。在LinuxThreads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。pthread_t类型在LinuxThreads中定义为无符号长整型。
#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2);判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1和thread2是否相等。
#include <pthread.h> int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); pthread_once_t once_control = PTHREAD_ONCE_INIT;本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
#include <stdio.h> #include <pthread.h> pthread_once_t once=PTHREAD_ONCE_INIT; void once_run(void) { printf("once_run in thread %d\n",pthread_self()); } void * child1(void *arg) { int tid=pthread_self(); printf("thread %d enter\n",tid); pthread_once(&once,once_run); printf("thread %d returns\n",tid); } void * child2(void *arg) { int tid=pthread_self(); printf("thread %d enter\n",tid); pthread_once(&once,once_run); printf("thread %d returns\n",tid); } int main(void) { int tid1,tid2; printf("hello\n"); pthread_create(&tid1,NULL,child1,NULL); pthread_create(&tid2,NULL,child2,NULL); sleep(10); printf("main thread exit\n"); return 0; } //once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INIT(LinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS(1)、DONE(2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0。