线程
进程是系统内部承担资源分配的基本单位,线程是系统内部调度的基本单位。进程强调资源独立,线程强调资源共享(线程栈独立)。线程的粒度更细相比于进程,所以多线程程序并发度更高,但更难于去管理
线程创建
int pthread_create(pthread_t * thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg)
线程id
默认thread_t 是一个无符号long类型,它是pthread库内的一个id,不属于Linux内核的。在Linux下没有线程概念,它是用进程去模拟线程的,所以申请一个线程可以看作是申请一个进程。一个进程组的各个线程调用getpid()返回的都是相同的pid是因为这个函数返回的是tpid(线程组id),gettid函数才是返回的是真真的pid。
#####线程属性
######线程属性创建与释放
pthread_attr_t是一个关于线程属性的结构体,我们并不能直接操作它的成员变量,Linux给我们提供了一些调用接口去操作它。
初始化和释放一个 pthread_attr_t 结构体,我们使用该结构体前是需要先初始化的,使用完毕后需要destroy,然后又可以继续重新init了。destroy的时候对一个使用过attr创建的线程是没有任何影响的。
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
线程同步或者分离设置
缺省为PTHREAD_CREATE_JOINABLE状态,这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
detach表示由系统回收我们不需要回收,joinable状态表示我们是需要主动回收的否则有类似于僵尸进程
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
线程调度设置
调度策略主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时/时间片调度)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对root有效。运行时可以用过pthread_setschedparam()来改变。
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
线程调度优先级设置
struct sched_param结构内目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。
struct sched_param {
int sched_priority; /* Scheduling priority */
};
int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param);
int pthread_attr_getschedparam(pthread_attr_t *attr,
struct sched_param *param);
创建线程的方式
有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度优先级(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);
int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);
cpu时间的设置
这个属性可以设置线程怎么去抢夺调度机会。线程可以只跟该进程组内的线程去抢夺机会,一般我们称它为用户级线程,所以该线程的调度是跟进程状态是分开的,可能该进程是Running态,但是该线程是阻塞态,一般由库去实现具体调度,内核不去管理这些线程。如果一个线程跟该内核的所有线程抢夺调度机会,那么它就是内核线程,所以一个内核线程的调度是由内核决定的与当前进程组的其他线程没有关系,Linux下只支持内核线程,都是1:1的实现即1个线程对应1个调度实体。
scope是PTHREAD_SCOPE_SYSTEM与PTHREAD_SCOPE_PROCESS选项。system的那个是内核线程,process是用户级线程,Linux只支持system设置。
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
Linux内核关于线程的实现
我们知道,Linux的线程实现是由进程实现的。内核提供了两个系统调用函数__clone()和fork(),最终都用不同的参数调用do_fork()系统调用。当然,要想实现线程,内核层面上对创建的多个线程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的。
Linux线程在内核是以轻量级进程的形式存在的,拥有独立的进程pcb,而所有的创建、同步、删除等操作都在应用层的pthread库中进行。pthread库使用一个管理线程(__pthread_manager(),每个进程都有唯一的一个)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。
线程取消
一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。
取消语义
线程取消的方法是向目标线程发Cancel信号,但如何处理该Cancel信号则由目标线程自己决定,有忽略、立即终止、继续运行至Cancelation-point(取消点)三个方式,由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是线程继续运行,只有运行至Cancelation-point的时候才会退出。
取消点
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是取消点,而其他pthread函数都不会引起Cancelation动作。
因此如果为了确保线程可以被即使cancel调我们可以这样做
void * PthreadFunc(void * arg)
{
pthread_testcancel();
....
retcode = read(fd, buffer, length);//sleep(10);也是取消点
....
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状态,如果是,则cancel线程,否则直接返回
static void * thread_func(void *ignored_argument)
{
int s;
/* Disable cancellation for a while, so that we don't
immediately react to a cancellation request */
s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
printf("thread_func(): started; cancellation disabled\n");
sleep(5);
printf("thread_func(): about to enable cancellation\n");
s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
/* sleep() is a cancellation point */
sleep(1000); /* Should get canceled while we sleep */
/* Should never get here */
printf("thread_func(): not canceled!\n");
return NULL;
}
等待线程
int pthread_join(pthread_t thread, void **retval)
回收线程跟waitpid一样