/*linux
演示linux线程的基本操做程序员
*/编程
#include <stdio.h>数据结构
#include <stdlib.h>多线程
#include <unistd.h>函数
#include <pthread.h>测试
#define __DEBUGui
#ifdef __DEBUGspa
#define DBG(fmt,args...) fprintf(stdout, fmt, ##args)线程
#else
#define DBG(fmt,args...)
#endif
#define ERR(fmt,args...) fprintf(stderr, fmt, ##args)
static int isThreadQuit = 0;
void SetXxThreadQuit()
{
/*quit*/
isThreadQuit = 1;
}
void *XxManageThread(void *arg)
{
char *cmd = (char*)arg;
DBG("arg value=%s\n",cmd);
while(isThreadQuit==0){
DBG("[1] thread running\n");
sleep(1);
}
/*arg是将指针带进来,cmd则相反,或者设置 NULL*/
pthread_exit(cmd);
//pthread_exit(NULL);
}
int XxManageThreadInit()
{
pthread_t tManageThread;
char *any="any value";
char *retn;
int ret;
/*
第二个参数是设置线程属性,通常不多用到(设置优先级等),第四个参数为传递到线程的指针,
能够为任何类型
*/
ret = pthread_create(&tManageThread,NULL,XxManageThread,any);
if(ret == -1){
/*成功返回0.失败返回-1*/
ERR("Ctreate Thread ERROR\n");
return -1;
}
/*
设置线程退出时资源的清理方式,若是是detach,退出时会自动清理
若是是join,则要等待pthread_join调用时才会清理
*/
pthread_detach(tManageThread);
//pthread_join(tManageThread,retn);
//DBG("retn value=%s\n",retn);
return 0;
}
#define TEST_MAIN
#ifdef TEST_MAIN
int main()
{
printf("hello liuyu\n");
int count=3;
if(XxManageThreadInit()==-1){
exit(1);
}
while(count--){
DBG("[0] main running\n");
sleep(2);
}
SetXxThreadQuit();
DBG("waitting thread exit...\n");
while(1);
return 0;
}
#endif
一 线程建立
pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
typedef unsigned long int pthread_t;
它是一个线程的标识符。函数pthread_create用来建立一个线程,它的原型为:
extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
void *(*__start_routine) (void *), void *__arg));
参数解释以下:
thread : 用于返回建立的线程的ID;每一个线程都有本身的ID,在线程内能够调用pthread_self()函数获取ID值,该函数原型是这样的:pthread_t pthread_self()。
attr : 用于指定将要被建立的线程的属性;该值能够为NULL,表示默认的属性。等下专门说下这个属性,暂时知道这个参数表示用于建立线程的方式就能够了;
start_routine : 这是一个函数的指针,指定线程被调度时的入口;
arg : 用于线程入口函数的参数;
这里,咱们的函数thread不须要参数,因此最后一个参数设为空指针。第二个参数咱们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改在下一节阐述。当建立线程成功时,函数返回0,若不为0则说明建立线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制建立新的线程,例如线程数目过多了;后者表示第二个参数表明的线程属性值非法。建立线程成功后,新建立的线程则运行参数三和参数四肯定的函数,原来的线程则继续运行下一行代码。
函数pthread_join用来等待一个线程的结束。函数原型为:
extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它能够用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象咱们上面的例子同样,函数结束了,调用它的线程也就结束了;另外一种方式是经过函数pthread_exit来实现。它的函数原型为:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
惟一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,不然第一个接收到信号的线程成功返回,其他调用pthread_join的线程则返回错误代码ESRCH。
在这一节里,咱们编写了一个最简单的线程,并掌握了最经常使用的三个函数pthread_create,pthread_join和pthread_exit。下面,咱们来了解线程的一些经常使用属性以及如何设置这些属性。
二 修改线程的属性
在上一节的例子里,咱们用pthread_create函数建立了一个线程,在这个线程中,咱们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来讲,使用默认属性就够了,但咱们仍是有必要来了解一下线程的有关属性。
属性结构为pthread_attr_t,它一样在头文件/usr/include/pthread.h中定义.线程属性由数据结构pthread_attr_t结构表示,其定义以下所示:
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的做用域
size_t guardsize; 线程栈末尾的警惕缓冲区大小
int stackaddr_set;
void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
detachstate: 线程的分离状态表示线程会以什么样的方式来终止本身,或者说该状态用来表示建立的线程是否保持与进程中其余的线程一种同步。若是这个没有被置位,那就不能用pthread_join调用来同步,当线程终止时,自行释放所占用的资源,而不用在pthread_join返回以后,再来释放。该值默认的状况是PTHREAD_CREATE_JOINABLE状态,在线程建立以后,能够调用pthread_detach()来设置为PTHREAD_CREATE_DETACH状态。一旦设置了这个状态建立线程以后,将不能被从新设为PTHREAD_CREATE_JOINABLE 状态;
schepolicy : 表示线程被调度的策略。主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时能够用过 pthread_setschedparam()来改变,该函数原型是这样的:int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param)。这个调用能够动态的改变调度策略和线程的优先级。
scheparam: 一个struct sched_param结构,结构体包含一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效,并能够在运行时经过pthread_setschedparam()函数来改变,缺省为0。
inheritshed : 表示线程建立的继承;调用函数pthread_attr_setinheritsched和pthread_attr_getinheritsched用来设置和获得线程的继承属性。有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),然后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
scope : 表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中全部线程一块儿竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。能够经过调用pthread_attr_getscope(), pthread_attr_setscope(),用于获取属性中的线程的做用域。
stacksize,stackaddr : 表示线程堆栈的大小和地址。也有四个函数用于设置和获取相应的值。pthread_attr_getstatcksize(),pthread_attr_setstatcksize(),pthread_attr_setstackaddr(),pthread_attr_getstatckaddr()。
guardsize : 控制着线程栈末尾以后以免栈溢出的扩展内存大小。一样能够调用pthread_attr_setguardsize()和pthread_attr_getguardsize()。
这个结构体在使用过程当中由pthread_attr_init和pthread_attr_destory负责数据的初始化和销毁;这个函数必须在pthread_create函数以前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程一样级别的优先级。
关于线程的绑定,牵涉到另一个概念:轻进程(LWP:Light Weight Process)。轻进程能够理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是经过轻进程来实现的,一个轻进程能够控制一个或多个线程。默认情况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种情况即称为非绑定的。绑定情况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具备较高的响应速度,这是由于CPU时间片的调度是面向轻进程的,绑定的线程能够保证在须要的时候它总有一个轻进程可用。经过设置被绑定的轻进程的优先级和调度级可使得绑定的线程知足诸如实时反应之类的要求。
设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即建立了一个绑定的线程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
线程的分离状态决定一个线程以什么样的方式来终止本身。在上面的例子中,咱们采用了线程的默认属性,即为非分离状态,这种状况下,原有的线程等待建立的线程结束。只有当pthread_join()函数返回时,建立的线程才算终止,才能释放本身占用的系统资源。而分离线程不是这样子的,它没有被其余的线程所等待,本身运行结束了,线程也就终止了,立刻释放系统资源。程序员应该根据本身的须要,选择适当的分离状态。设置线程分离状态的函数为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>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, ¶m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&tid, &attr, (void *)myfunction, myarg);
三 终止线程
分为如下几种:
一、正常终止
线程在执行完成以后,正常终止。
二、线程取消
通常状况下,线程在其主体函数退出的时候会自动终止,但同时也能够由于接收到另外一个线程发来的终止(取消)请求而而强制终止。
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程本身决定,或者忽略、或者当即终止、或者继续运行至Cancelation-point(取消点),由不一样的Cancelation状态决定。线程接收到CANCEL信号的缺省处理(即pthread_create()建立线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引发阻塞的系统调用都是Cancelation-point,而其余pthread函数都不会引发Cancelation动做。可是pthread_cancel的手册页声称,因为LinuxThread库与C库结合得很差,于是目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,所以能够在须要做为Cancelation-point的系统调用先后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标,即以下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
程序设计方面的考虑
若是线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程没法由外部其余线程的取消请求而终止。所以在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
与线程取消相关的pthread函数
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状态,若是是,则进行取消动做,不然直接返回。
3,pthread_exit
在线程内显示的调用,以终止线程。
4,等待终止
在一个线程里面,咱们要等待另外一个线程结束。能够经过调用pthread_join函数。这将会在调用线程引发阻塞,而且在建立thread所指定的线程时,其分离属性必须是pthread_create_joinable.不然调用出错。
五、杀死线程
该函数能够用于向指定的线程发送信号:
int pthread_kill(pthread_t threadId,int signal);
若是线程内不对信号进行处理,则调用默认的处理程式,如SIGQUIT会退出终止线程,SIGKILL会杀死线程等等,能够调用signal(SIGQUIT, sig_process_routine); 来自定义信号的处理程序。
传递的pthread_kill的signal参数通常都是大于0的,这时系统默认或者自定义的都是有相应的处理程序。signal为0时,是一个被保留的信号,通常用这个保留的信号测试线程是否存在。
pthread_kill 返回值以下:
0:调用成功。
ESRCH:线程不存在。
EINVAL:信号不合法。
int kill_ret = pthread_kill(thread_id,0);
if(kill_ret == ESRCH)
printf("指定的线程不存在或者是已经终止\n");
else if(kill_ret == EINVAL)
printf("调用传递一个无用的信号\n");
else
printf("线程存在\n");