线程
编译:
-lpthreadLinux系统下的多线程遵循POSIX线程接口,称为pthread,通过系统调用clone()来实现。使用头文件pthread.h。
创建
第一个参数为指向线程标识符的指针。
等待
pthread_join()以阻塞的方式等待thread指定的线程结束
thread: 线程标识符,即线程ID。 retval: 用户定义的指针,用来存储被等待线程的返回值。
pthread_join(p, NULL);
退出
绑定
轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
设置线程绑定状态的函数为 pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。
#include <pthread.h>
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); //绑定
pthread_create(&tid, &attr, (void *) my_function, NULL);
线程分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。非分离的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用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返回。设置一段等待时间,是在多线程编程里常用的方法。
优先级
它存放在结构sched_param中。用函数pthread_attr_getschedparam进行提取和存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。
#include <pthread.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);
可被多个函数调用。 每个函数调用给它赋予各自的值,函数之间互不影响
和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键
该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。这个函数常和函数pthread_once 一起使用,为了让这个键只被创建一次。
函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。
要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。
我们创建一个键,并将它和某个数据相关联。我们要定义一个函数 createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据/* 声明一个键*/
/* 函数 createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey,创建键*/
pthread_once ( & once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}
/* 函数 createMyKey,创建一个键,并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&myWinKey, freeWinKey);
}
/* 函数 freeWinKey,释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
}
线程取消(pthread_cancel)
基本概念
pthread_cancel调用并不等于线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,
直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置.
与线程取消相关的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)
是说pthread_testcancel在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求.
线程取消功能处于启用状态且取消状态设置为延迟状态时,pthread_testcancel()函数有效。
如果在取消功能处处于禁用状态下调用pthread_testcancel(),则该函数不起作用。
请务必仅在线程取消线程操作安全的序列中插入pthread_testcancel()。除通过pthread_testcancel()调用以编程方式建立的取消点意外,pthread标准还指定了几个取消点。测试退出点,就是测试cancel信号.
取消点:
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
pthreads标准指定了几个取消点,其中包括:
(1)通过pthread_testcancel调用以编程方式建立线程取消点。
(2)线程等待pthread_cond_wait或pthread_cond_timewait()中的特定条件。
(3)被sigwait(2)阻塞的函数
(4)一些标准的库调用。通常,这些调用包括线程可基于阻塞的函数。
缺省情况下,将启用取消功能。有时,您可能希望应用程序禁用取消功能。如果禁用取消功能,则会导致延迟所有的取消请求,
直到再次启用取消请求。
根据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()调用.
取消类型(Cancellation Type)
我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。
因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。
POSIX的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。
线程终止的清理工作
Posix的线程终止有两种情况:正常终止和非正常终止。
线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;
非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在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() 的调用将在清理函数栈中形成一个函数链;
从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)
都将执行pthread_cleanup_push()所指定的清理函数。
在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到 pthread_cleanup_pop()时
是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
#include <stdio.h>
#include <pthread.h>
#define THREAD_NUM 3
#define REPEAT_TIMES 5
#define DELAY 4
pthread_mutex_t mutex;
void *thrd_func(void *arg);
int main(){
pthread_t thread[THREAD_NUM];
int no;
void *tret;
srand((int)time(0));
// 创建快速互斥锁(默认),锁的编号返回给mutex
pthread_mutex_init(&mutex,NULL);
// 创建THREAD_NUM个线程,每个线程号返回给&thread[no],每个线程的入口函数均为thrd_func,参数为
for(no=0;no<THREAD_NUM;no++){
if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) {
printf("Create thread %d error!\n",no);
exit(1);
} else
printf("Create thread %d success!\n",no);
}
// 对每个线程进行join,返回值给tret
for(no=0;no<THREAD_NUM;no++){
if (pthread_join(thread[no],&tret)!=0){
printf("Join thread %d error!\n",no);
exit(1);
}else
printf("Join thread %d success!\n",no);
}
// 消除互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
void *thrd_func(void *arg){
int thrd_num=(void*)arg; // 传入的参数,互斥锁的编号
int delay_time,count;
// 对互斥锁上锁
if(pthread_mutex_lock(&mutex)!=0) {
printf("Thread %d lock failed!\n",thrd_num);
pthread_exit(NULL);
}
printf("Thread %d is starting.\n",thrd_num);
for(count=0;count<REPEAT_TIMES;count++) {
delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1;
sleep(delay_time);
printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time);
}
printf("Thread %d is exiting.\n",thrd_num);
// 解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
如果不加互斥锁,CPU会将三个线程随机顺序,每个线程执行一会,再换另一个。直到结束
sem_wait()和sem_trywait(): P操作,在信号量大于零时将信号量的值减一
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define THREAD_NUM 3
#define REPEAT_TIMES 5
#define DELAY 4
sem_t sem[THREAD_NUM];
void *thrd_func(void *arg);
int main(){
pthread_t thread[THREAD_NUM];
int no;
void *tret;
srand((int)time(0));
// 初始化THREAD_NUM-1个信号量,均初始化为0
for(no=0;no<THREAD_NUM-1;no++){
sem_init(&sem[no],0,0);
}
// sem[2]信号量初始化为1,即sem数组中最后一个信号量
sem_init(&sem[2],0,1);
// 创建THREAD_NUM个线程,入口函数均为thrd_func,参数为(void*)no
for(no=0;no<THREAD_NUM;no++){
if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) {
printf("Create thread %d error!\n",no);
exit(1);
} else
printf("Create thread %d success!\n",no);
}
// 逐个join掉THREAD_NUM个线程
for(no=0;no<THREAD_NUM;no++){
if (pthread_join(thread[no],&tret)!=0){
printf("Join thread %d error!\n",no);
exit(1);
}else
printf("Join thread %d success!\n",no);
}
// 逐个取消信号量
for(no=0;no<THREAD_NUM;no++){
sem_destroy(&sem[no]);
}
return 0;
}
void *thrd_func(void *arg){
int thrd_num=(void*)arg; // 参数no
int delay_time,count;
// 带有阻塞的p操作
sem_wait(&sem[thrd_num]);
printf("Thread %d is starting.\n",thrd_num);
for(count=0;count<REPEAT_TIMES;count++) {
delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1;
sleep(delay_time);
printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time);
}
printf("Thread %d is exiting.\n",thrd_num);
// 对前一个信号量进行V操作
// 由于只有最后一个信号量初始化为1,其余均为0
// 故线程执行的顺序将为逆序
sem_post(&sem[(thrd_num+THREAD_NUM-1)%THREAD_NUM]);
pthread_exit(NULL); // 线程主动结束
}