pthread

来源:互联网 发布:apache-ant-zip.jar 编辑:程序博客网 时间:2024/06/12 19:44

Pthread

POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32。
线程库实行了POSIX线程标准通常称为Pthreads。POSIX线程具有很好的可移植性,使用pthreads编写的代码可运行于Solaris、FreeBSD、Linux 等平台,Windows平台亦有pthreads-win32可供使用. Pthreads定义了一套C语言的类型、函数与常量,它以pthread.h头文件和一个线程库实现。
pthread_t:线程ID
pthread_attr_t:线程属性
pthread_create():创建一个线程
pthread_exit():终止当前线程
pthread_cancel():中断另外一个线程的运行
pthread_join():阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init():初始化线程的属性
pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate():获取脱离状态的属性
pthread_attr_destroy():删除线程的属性
pthread_kill():向线程发送一个信号
用于 mutex 和条件变量
pthread_mutex_init() 初始化互斥锁
pthread_mutex_destroy() 删除互斥锁
pthread_mutex_lock():占有互斥锁(阻塞操作)
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
pthread_mutex_unlock(): 释放互斥锁
pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait(): 等待条件变量的特殊条件发生
Thread-local storage(或者以Pthreads术语,称作线程特有数据):
pthread_key_create(): 分配用于标识进程中线程特定数据的键
pthread_setspecific(): 为指定线程特定数据键设置线程特定绑定
pthread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete(): 销毁现有线程特定数据键
pthread_attr_getschedparam();获取线程优先级
pthread_attr_setschedparam();设置线程优先级
pthread_equal(): 对两个线程的线程标识号进行比较
pthread_detach(): 分离线程
pthread_self(): 查询线程自身线程标识号


int  pthread_create(pthread_t  *  thread,

pthread_attr_t * attr,

void * (*start_routine)(void *),

void * arg)

thread:返回创建的线程的ID

attr:线程属性,调度策略、优先级等都在这里设置,如果为NULL则表示用默认属性

start_routine:线程入口函数,可以返回一个void*类型的返回值,该返回值可由pthread_join()捕获

arg:传给start_routine的参数,可以为NULL

返回值:成功返回0

设置线程属性

线程属性通过attr进行设置。

设置与查询attr结构的为pthread_attr_get***()pthread_attr_set***()两个函数系列。

设置与查询线程参数的为pthread_get***()pthread_set***()两个函数系列。也可以在创建时通过pthrea_create传入参数。

注:有些必须在线程创建时设置,如调度策略。

调度策略

调度策略有三种:

SCHED_OTHER:非实时、正常

SCHED_RR:实时、轮询法

SCHED_FIFO:实时、先入先出,与vxworks的调度机制一致

例程:

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setschedpolicy(&attr, SCHED_FIFO);//sched_policy

优先级

线程优先级支持两种设置方式,一种是创建时设置,另一种是创建后动态设置

例程:

pthread_attr_setschedparam(&attr, new_priority);

如果是静态设置,则之后会用该属性创建任务,如果是动态设置,则使用下列函数设置:

pthread_attr_setschedparam(&attr, &task->prv_priority);

pthread_attr_getschedparam(&attr, &schedparam);

schedparam.sched_priority = new_priority;

pthread_attr_setschedparam(&attr, &schedparam);

pthread_setschedparampthrid, sched_policy, &schedparam);

脱离同步

Pthread_join()函数可以使主线程与子线程保持同步。如果设置了detachstate状态,则pthread_join()会失效,线程会自动释放所占用的资源。线程的缺省状态为PHREAD_CREATE_JOINABLE状态,线程运行起来后,一旦被设置为PTHREAD_CREATE_DETACH状态,则无法再恢复到joinable状态。

例程:

pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);、

调度继承

线程A创建了线程B,则线程B的调度策略是与线程A的调度策略与线程B的继承策略有关的。如果线程B继承策略为PTHREAD_INHERIT_SCHED,则线程B的调度策略与线程A相同;如果线程B继承策略为PTHREAD_EXPLICIT_SCHE,则线程B的调度策略由attr决定。

pthread_attr_setdetachstate (&attr,PTHREAD_CREATE_DETACHED);

线程取消定义

Pthread线程可以通过发送取消请求的方式终止一个线程的运行。

取消线程的操作主要应用于下列场景中:有一个线程在使用select监控网口,主控线程此时接到了用户的通知,要放弃监听,则主控线程会向监听线程发送取消请求。

Linuxpthread线程接收到取消请求时,并不会立刻终止线程,而是要等到取消点时才会结束任务。这样我们可以为取消点建立某些特殊的处理。Select是取消点,所以可以退出。


取消点

Vxworks可以在任意位置杀死任务,这样做导致任务被杀死后的位置不可控,所以vxworks中不会很少使用taskDeleteHook

Pthread规定了取消点的概念。不论线程何时收到取消请求,都只能在取消点上才能取消线程。这就保证了风险的可控。

Pthread标准指定了以下几个取消点:

Ø  pthread_testcancel

Ø  所有调度点,如pthread_cond_waitsigwaitselectsleep

根据POSIX标准,read()write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

pthread_testcancel();

retcode = read(fd, buffer, length);

pthread_testcancel();

这段代码可以保证read附近有取消点,但是否有可能会卡在read中无法返回呢?

 

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

线程取消函数

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状态,如果是,则进行取消动作,否则直接返回。

线程终止方式

线程终止有两种情况(不考虑进程),一种是线程主体函数return时,线程会自动终止,此时的退出是可预知的;另一种是其它线程向通以进程下的线程发送取消请求,线程会根据情况判断是否终止,此时的终止是不可预知的。

Vxworks中的任务与此类似。任务的主体函数return时,任务会自动终止;其它任务调用taskDelete()可以杀死任意一个任务。TaskDelete()并不安全,因为任务可能被杀死在任意一个时刻。



  线程终止时的清理

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源,相当于是增加了一个析构函数。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。

API定义如下:

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为执行;这个参数并不影响异常终止时清理函数的执行。

 

这两个其实是宏,必须成对出现,否则会编译不过。

编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

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模式。为此,pthread中还提供了一对不保证可移植的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);

 }

线程终止的同步及其返回值

一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。

void pthread_exit(void *retval)

int pthread_join(pthread_t th, void **thread_return)

int pthread_detach(pthread_t th)

 

pthread_join()的调用者将挂起并等待th线程终止,retvalpthread_exit()调用者线程(线程IDth)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。

如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。

一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收。

关于ptread_exite()return

理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。

在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。

其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault


判断是否为同一个线程

int pthread_equal(pthread_t thread1, pthread_t thread2)

判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1thread2是否相等。

  仅执行一次的操作

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

本函数使用初值为PTHREAD_ONCE_INITonce_control变量保证init_routine()函数在本进程执行序列中仅执行一次。这个类似与线程的构造函数。

once_run()函数仅执行一次,且究竟在哪个线程中执行是不定的,尽管pthread_once(&once,once_run)出现在两个线程中。

LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。如果once_control的初值不是PTHREAD_ONCE_INITLinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER0)、IN_PROGRESS1)、DONE2),如果once初值设为1,则由于所有pthread_once()都必须等待其中一个激发"已执行一次"信号,因此所有pthread_once()都会陷入永久的等待中;如果设为2,则表示该函数已执行过一次,从而所有pthread_once()都会立即返回0

pthread_kill_other_threads_np()

void pthread_kill_other_threads_np(void)

这个函数是LinuxThreads针对本身无法实现的POSIX约定而做的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另一个程序时,当前进程的所有线程都应终止。由于LinuxThreads的局限性,该机制无法在exec中实现,因此要求线程执行exec前手工终止其他所有线程。pthread_kill_other_threads_np()的作用就是这个。

需要注意的是,pthread_kill_other_threads_np()并没有通过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使所有其他线程都结束运行,而不经过Cancel动作,当然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中却是用的__pthread_sig_cancel信号来kill线程,应该效果与执行pthread_cancel()是一样的,其中原因目前还不清楚。 TSD概念

在单线程程序中,我们经常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问,比如程序可能需要每个线程维护一个链表,而使用相同的函数操作,最简单的办法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由Posix线程库维护,称为线程私有数据(Thread-specific Data,或TSD)。

创建与注销

Posix定义了两个API分别用来创建和注销TSD:

int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)) 该函数从TSD池中分配一项,将其值赋给key供以后访问使用。如果destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。不论哪个线程调用pthread_key_create(),所创建的key都是所有线程可访问的,但各个线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };创建一个TSD就相当于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,然后设置destructor函数为destr_function。注销一个TSD采用如下API:int pthread_key_delete(pthread_key_t key)这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。6.3.  访问TSD的读写都通过专门的Posix Thread函数进行,其API定义如下:int  pthread_setspecific(pthread_key_t  key,  const   void  *pointer)void * pthread_getspecific(pthread_key_t key)写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void *,因此可以指向任何类型的数据。在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由以下几个宏来说明:#define PTHREAD_KEY_2NDLEVEL_SIZE       32#define PTHREAD_KEY_1STLEVEL_SIZE   \((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)/ PTHREAD_KEY_2NDLEVEL_SIZE)    其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,    因此一维数组大小为32。而具体存放的位置由key值经过以下计算得到:idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZEidx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE

也就是说,数据存放与一个32×32的稀疏矩阵中。同样,访问的时候也由key值经过类似计算得到数据所在位置索引,再取出其中内容返回。

互斥锁(互斥信号量)

Pthread的互斥锁与vxworks的互斥信号量类似,都是用于互斥保护。

需要注意的是,linux有取消点的概念,即任务在运行时可以被取消。如果使用了互斥锁,可能会造成为解锁就被取消。为了解决这一问题,linux提供了可以在取消上加回调函数的功能:

pthread_cleanup_push()

pthread_cleanup_pop()

两个必须成对出现如果线程运行到两个函数之间被取消时,push注册的函数会被调用。

例程:

如果没有pushpop两行,则tid1被杀死后会导致tid2无法获取到互斥锁。
条件变量(同步信号量)

Pthread的条件变量与vxworks中同步信号量类似,都是用于任务同步。区别是条件变量的操作不是原子操作,需要借助互斥锁保证其原子性。7.3.  信号灯(计数信号量)

Pthread的信号灯与vxworks中计数信号量信号量类似,都是用于表示资源是否可用。


pthread_attr_setdetachstate

在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

        线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。

设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

线程等待——正确处理线程终止

#include <pthread.h>

void pthread_exit(void *retval);

void pthread_join(pthread_t th,void *thread_return);//挂起等待th结束,*thread_return=retval;

int pthread_detach(pthread_t th);

如果线程处于joinable状态,则只能只能被创建他的线程等待终止。

在Linux平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。(说明:线程处于joinable状态下)

调用该函数的线程将挂起,等待 th 所表示的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对 th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。

如果不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态。另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。

创建 detach 线程:

pthread_t tid;

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

总之为了在使用 pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。

0 0