本文主要是介绍二十九 多线程编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
pthread_cancel(3)
2007-11-20 09:14 星期二
PTHREAD_CANCEL(3)
名称
pthread_cancel, pthread_setcancelstate, pthread_setcanceltype,
pthread_testcancel – 取消线程
大纲
#include
int pthread_cancel(pthread_t thread);
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
void pthread_testcancel(void);
描述
取消是一种让一个线程可以结束其它线程的机制。更好的是,一个线程可以对另一个线程发送一个结束的请求。依据设置的不同,目标线程可能会置之不理,可能会立即终止也可能会将它推迟到下一个取消点。
当一个线程最终尊重了取消的请求,它的行为就像执行了pthread_exit(PTHREAD_CANCEL):所有的清理函数句柄以想反的次序被调用,线程终止函数被调用,最终结束线程的执行,并且返回PTHREAD_CANCEL。可以查看pthread_exit(3)获得更多的信息。
pthread_exit函数向thread发送一个取消执行的请求。
pthread_setcancelstate函数更改目标线程的取消状态――即,是否忽略这个请求。参数state是新的取消状态:要么是PTHREAD_CANCEL_ENABLE,即允许取消,要么是PTHREAD_CANCEL_DISABLE,即不允许取消(收到的取消请求将被忽略)。如果oldstate参数非空,那么它的值会存储于oldstate参数中,以便以后其它的线程可以用这个值来恢复。
pthread_setcanceltype函数改变目标线程对于取消请求的响应:异步的(立即执行)或是延时。参数type是新的类型:或是PTHREAD_CANCEL_ASYNCHRONOUS,请求一到达就立刻执行,或是PTHREAD_CANCEL_DEFERRED,挂起收到的信号直到下个取消点。如果oldtype非空,以前的取消状态会存储于oldtype中,以便以后其它线程再使用pthread_setcanceltype来恢复。
用pthread_create(3)创建的线程常常是可取消并且延时的。那是因为默认的状态是PTHREAD_CANCEL_ENABLE,类型则是PTHREAD_CANCEL_DEFERRED。
取消点是在程序在运行的时候检测是否收到取消请求,是否允许允许操作执行的点。下面的POSIX线程函数就是取消点:
pthread_join(3)
pthread_cond_wait(3)
pthread_cond_timedwait(3)
pthread_testcancel(3)
sem_wait(3)
sigwait(3)
所有其它的POSIX线程函数都保证不是取消点。那是因为它们从来不会在延时取消操作的模式下进行取消操作。
pthread_testcancel函数除了测试未决的取消并且执行它们之外不做其它事。目的是在长串的代码中引入外部检测取消点而不必调用其它的取消点函数。
返回值
pthread_cancel, pthread_setcancelstate和pthread_setcanceltype在成功的时候返回零,在出错的时候返回非零的出错码。
错误码
pthread_cancel在出错时返回下面的出错码:
ESRCH:没有找到thread指定ID的线程。
pthread_setcancelstate在出错时返回下面的出错码:
EINVAL:state参数的值并非PTHREAD_CANCEL_ENABLE或PTHREAD_CANCEL_DISABLE。
pthread_setcanceltype在出错时返回下面的出错码:
EINVAL:type的值并非PTHREAD_CANCEL_DEFERRED 或PTHREAD_CANCEL_ASYNCHRONOUS。
作者
Xavier Leroy
参见
pthread_exit(3), pthread_cleanup_push(3), pthread_cleanup_pop(3)
BUGS
POSIX指定了一系列的系统调用(基本上,所有的系统调用都会阻塞,比如read(2),write(2),wait(2)等等)和一些可能使用这些系统调用的库为取消点。LinuxThreads的C库对这些的实现还不充分,因此,没有任何C库的函数是取消点。
至少对于系统调用,有一个工作区。取消请求发送到目标线程是通过发送一个信号来完成。信号会中断所有阻塞的系统调用,并且导致它们立即返回一个EINTR错误。所以,比如要在进行系统调用read的期间进行取消检查,可以像下面的这样来完成:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
4 pthread_attr_init线程属性
1.线程属性
线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化,在使用后需要对其去除初始化。我们用pthread_attr_init函数对其初始化,用pthread_attr_destroy对其去除初始化。
1.
名称:: | pthread_attr_init/pthread_attr_destroy |
功能: | 对线程属性初始化/去除初始化 |
头文件: | #include<pthread.h> |
函数原形: | int pthread_attr_init(pthread_attr_t*attr); int pthread_attr_destroy(pthread_attr_t*attr); |
参数: | Attr 线程属性变量 |
返回值: | 若成功返回0,若失败返回-1。 |
调用pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。
如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。
线程属性结构如下:
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
structsched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void* stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
每个个属性都对应一些函数对其查看或修改。下面我们分别介绍。
二、线程的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。
2.
名称:: | pthread_attr_getdetachstate/pthread_attr_setdetachstate |
功能: | 获取/修改线程的分离状态属性 |
头文件: | #include<pthread.h> |
函数原形: | int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr,intdetachstate); |
参数: | Attr 线程属性变量 Detachstate 线程的分离状态属性 |
返回值: | 若成功返回0,若失败返回-1。 |
可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程。可以使用pthread_attr_getdetachstate函数获取当前的datachstate线程属性。
以分离状态创建线程
#iinclude<pthread.h>
void *child_thread(void *arg) { printf(“child thread run!\n”); }
int main(int argc,char *argv[ ]) { pthread_ttid; pthread_attr_tattr;
pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); pthread_create(&tid,&attr,fn,arg); pthread_attr_destroy(&attr); sleep(1); } |
三、线程的继承性
函数pthread_attr_setinheritsched和pthread_attr_getinheritsched分别用来设置和得到线程的继承性,这两个函数的定义如下:
3.
名称:: | pthread_attr_getinheritsched pthread_attr_setinheritsched |
功能: | 获得/设置线程的继承性 |
头文件: | #include<pthread.h> |
函数原形: | int pthread_attr_getinheritsched(const pthread_attr_t*attr,int *inheritsched); int pthread_attr_setinheritsched(pthread_attr_t *attr,intinheritsched); |
参数: | attr 线程属性变量 inheritsched 线程的继承性 |
返回值: | 若成功返回0,若失败返回-1。 |
这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是继承性或指向继承性的指针。继承性决定调度的参数是从创建的进程中继承还是使用在schedpolicy和schedparam属性中显式设置的调度信息。Pthreads不为inheritsched指定默认值,因此如果你关心线程的调度策略和参数,必须先设置该属性。
继承性的可能值是PTHREAD_INHERIT_SCHED(表示新现成将继承创建线程的调度策略和参数)和PTHREAD_EXPLICIT_SCHED(表示使用在schedpolicy和schedparam属性中显式设置的调度策略和参数)。
如果你需要显式的设置一个线程的调度策略或参数,那么你必须在设置之前将inheritsched属性设置为PTHREAD_EXPLICIT_SCHED.
下面我来讲进程的调度策略和调度参数。我会结合下面的函数给出本函数的程序例子。
四、线程的调度策略
函数pthread_attr_setschedpolicy和pthread_attr_getschedpolicy分别用来设置和得到线程的调度策略。
4.
名称:: | pthread_attr_getschedpolicy pthread_attr_setschedpolicy |
功能: | 获得/设置线程的调度策略 |
头文件: | #include<pthread.h> |
函数原形: | int pthread_attr_getschedpolicy(const pthread_attr_t*attr,int *policy); int pthread_attr_setschedpolicy(pthread_attr_t *attr,intpolicy); |
参数: | attr 线程属性变量 policy 调度策略 |
返回值: | 若成功返回0,若失败返回-1。 |
这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是调度策略或指向调度策略的指针。调度策略可能的值是先进先出(SCHED_FIFO)、轮转法(SCHED_RR),或其它(SCHED_OTHER)。
SCHED_FIFO策略允许一个线程运行直到有更高优先级的线程准备好,或者直到它自愿阻塞自己。在SCHED_FIFO调度策略下,当有一个线程准备好时,除非有平等或更高优先级的线程已经在运行,否则它会很快开始执行。
SCHED_RR(轮循)策略是基本相同的,不同之处在于:如果有一个SCHED_RR
策略的线程执行了超过一个固定的时期(时间片间隔)没有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同优先级的线程准备好时,运行的线程将被抢占以便准备好的线程可以执行。
当有SCHED_FIFO或SCHED_RR策赂的线程在一个条件变量上等持或等持加锁同一个互斥量时,它们将以优先级顺序被唤醒。即,如果一个低优先级的SCHED_FIFO线程和一个高优先织的SCHED_FIFO线程都在等待锁相同的互斥且,则当互斥量被解锁时,高优先级线程将总是被首先解除阻塞。
五、线程的调度参数
函数pthread_attr_getschedparam 和pthread_attr_setschedparam分别用来设置和得到线程的调度参数。
5.
名称:: | pthread_attr_getschedparam pthread_attr_setschedparam |
功能: | 获得/设置线程的调度参数 |
头文件: | #include<pthread.h> |
函数原形: | int pthread_attr_getschedparam(const pthread_attr_t*attr,struct sched_param *param); int pthread_attr_setschedparam(pthread_attr_t *attr,conststruct sched_param *param); |
参数: | attr 线程属性变量 param sched_param结构 |
返回值: | 若成功返回0,若失败返回-1。 |
这两个函数具有两个参数,第1个参数是指向属性对象的指针,第2个参数是sched_param结构或指向该结构的指针。结构sched_param在文件/usr/include/bits/sched.h中定义如下:
struct sched_param
{
intsched_priority;
};
结构sched_param的子成员sched_priority控制一个优先权值,大的优先权值对应高的优先权。系统支持的最大和最小优先权值可以用sched_get_priority_max函数和sched_get_priority_min函数分别得到。
注意:如果不是编写实时程序,不建议修改线程的优先级。因为,调度策略是一件非常复杂的事情,如果不正确使用会导致程序错误,从而导致死锁等问题。如:在多线程应用程序中为线程设置不同的优先级别,有可能因为共享资源而导致优先级倒置。
6.
http://linux.die.net/man/3/sched_get_priority_min
#include <sched.h>
int sched_get_priority_max(int policy);int sched_get_priority_min(int policy);
The sched_get_priority_max() and sched_get_priority_min() functions shall return the appropriate maximum or minimum, respectively, for the scheduling policy specified bypolicy.
The value of policy shall be one of the scheduling policy values defined in <sched.h>.
If successful, the sched_get_priority_max() and sched_get_priority_min() functions shall return the appropriate maximum or minimum values, respectively. If unsuccessful, they shall return a value of -1 and set errno to indicate the error.
The sched_get_priority_max() and sched_get_priority_min() functions shall fail if:
EINVAL
The value of the policy parameter does not represent a defined scheduling policy.
下面是上面几个函数的程序例子:
#include <string.h>
#include<pthread.h>
#include<sched.h>
void *child_thread(void *arg)
{
int policy = 0;
int max_priority = 0,min_priority = 0;
struct sched_param param;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);
pthread_attr_getinheritsched(&attr,&policy);
if(policy == PTHREAD_EXPLICIT_SCHED){
printf("Inheritsched:PTHREAD_EXPLICIT_SCHED\n");
}
if(policy == PTHREAD_INHERIT_SCHED){
printf("Inheritsched:PTHREAD_INHERIT_SCHED\n");
}
pthread_attr_setschedpolicy(&attr,SCHED_RR);
pthread_attr_getschedpolicy(&attr,&policy);
if(policy == SCHED_FIFO){
printf("Schedpolicy:SCHED_FIFO\n");
}
if(policy == SCHED_RR){
printf("Schedpolicy:SCHED_RR\n");
}
if(policy == SCHED_OTHER){
printf("Schedpolicy:SCHED_OTHER\n");
}
max_priority = sched_get_priority_max(policy);
min_priority = sched_get_priority_min(policy);
printf("Maxpriority:%u\n",max_priority);
printf("Minpriority:%u\n",min_priority);
param.sched_priority = max_priority;
pthread_attr_setschedparam(&attr,¶m);
printf("sched_priority:%u\n",param.sched_priority);
pthread_attr_destroy(&attr);
}
int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL);
pthread_join(child_thread_id,NULL);
}
==[23]==gaoke@dev64_23:~/code$g++ -o test gaoke.cpp -lpthread
==[23]==gaoke@dev64_23:~/code$./test
Inheritsched:PTHREAD_EXPLICIT_SCHED
Schedpolicy:SCHED_RR
Maxpriority:99
Minpriority:1
sched_priority:99
5 pthread_create函数的详细讲解(包括向线程函数传递参数详解)
(1)线程函数只有一个参数的情况:直接定义一个变量通过应用传给线程函数。
例子:
#include <iostream>
#include <pthread.h>
using namespace std;
pthread_t thread;
void fn(void *arg)
{
int i = *(int *)arg;
cout<<"i = "<<i<<endl;
return ((void *)0);
}
int main()
{
int err1;
int i=10;
err1 = pthread_create(&thread, NULL, fn, &i);
pthread_join(thread, NULL);
}
2、线程函数有多个参数的情况:这种情况就必须申明一个结构体来包含所有的参数,然后在传入线程函数,具体如下:
例子:
首先定义一个结构体:
struct parameter
{
int size,
int count;
。。。。。
。。。
};
然后在main函数将这个结构体指针,作为void *形参的实际参数传递
struct parameter arg;
通过如下的方式来调用函数:
pthread_create(&ntid, NULL, fn,& (arg));
函数中需要定义一个parameter类型的结构指针来引用这个参数
void fn(void *arg)
{
int i = *(int *)arg;
cout<<"i = "<<i<<endl;
return ((void *)0);
}
void thr_fn(void *arg)
{
struct parameter *pstru;
pstru = ( struct parameter *) arg;
然后在这个函数中就可以使用指针来使用相应的变量的值了。
}
6 如何正确的终止正在运行的子线程
最近开发一些东西,线程数非常之多,当用户输入Ctrl+C的情形下,默认的信号处理会把程序退出,这时有可能会有很多线程的资源没有得到很好的释放,造成了内存泄露等等诸如此类的问题,本文就是围绕着这么一个使用场景讨论如何正确的终止正在运行的子线程。其实本文更确切的说是解决如何从待终止线程外部安全的终止正在运行的线程
首先我们来看一下,让当前正在运行的子线程停止的所有方法
1.任何一个线程调用exit
2.pthread_exit
3.pthread_kill
4.pthread_cancel
下面我们一一分析各种终止正在运行的程序的方法
任何一个线程调用exit
任何一个线程只要调用了exit都会导致进程结束,各种子线程当然也能很好的结束了,可是这种退出会有一个资源释放的问题.我们知道当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。没错,标准C++ IO流也会很好的在exit退出时得到flush并且释放资源,这些东西并不会造成资源的浪费(系统调用main函数入口类似于exit(main(argc,argv))).表面上似乎所有的问题都能随着进程的结束来得到很好的处理,其实并不然,我们程序从堆上分配的内存就不能得到很好的释放,如new ,delete后的存储空间,这些空间进程结束并不会帮你把这部分内存归还给内存.(本文初稿时,因基础不牢固,此处写错,事实上无论进程这样结束,系统都将会释放掉所有代码所申请的资源,无论是堆上的还是栈上的。(感谢ZKey的指导)。这种结束所有线程(包括主线程)的方式实际上在很多时候是非常可取的,但是对于针对关闭时进行一些别的逻辑的处理(指非资源释放逻辑)就不会很好,例如我想在程序被kill掉之前统计一下完成了多少的工作,这个统计类似于MapReduce,需要去每个线程获取,并且最后归并程一个统一的结果等等场景)
pthread_exit
此函数的使用场景是当前运行的线程运行pthread_exit得到退出,对于各个子线程能够清楚地知道自己在什么时候结束的情景下,非常好用,可是实际上往往很多时候一个线程不能知道知道在什么时候该结束,例如遭遇Ctrl+C时,kill进程时,当然如果排除所有的外界干扰的话,那就让每个线程干完自己的事情后,然后自觉地乖乖的调用pthread_exit就可以了,这并不是本文需要讨论的内容,本文的情景就是讨论如何处理特殊情况。
这里还有一种方法,既然子线程可以通过pthread_exit来正确退出,那么我们可以在遭遇Ctrl+C时,kill进程时处理signal信号,然后分别给在某一个线程可以访问的公共区域存上一个flag变量,线程内部每运行一段时间(很短)来检查一下flag,若发现需要终止自己时,自己调用pthread_exit,此法有一个弱点就是当子线程需要进行阻塞的操作时,可能无暇顾及检查flag,例如socket阻塞操作。如果你的子线程的任务基本没有非阻塞的函数,那么这么干也不失为一种很好的方案。
pthread_kill
不要被这个可怕的邪恶的名字所吓倒,其实pthread_kill并不像他的名字那样威力大,使用之后,你会感觉,他徒有虚名而已
pthread_kill的职责其实只是向指定的线程发送signal信号而已,并没有真正的kill掉一个线程,当然这里需要说明一下,有些信号的默认行为就是exit,那此时你使用pthread_kill发送信号给目标线程,目标线程会根据这个信号的默认行为进行操作,有可能是exit。当然我们同时也可以更改获取某个信号的行为,以此来达到我们终止子线程的目的。
1 #define _MULTI_THREADED
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <signal.h>
5 #include "check.h"
6
7 #define NUMTHREADS 3
8 void sighand(int signo);
9
10 void *threadfunc(void *parm)
11 {
12 pthread_t self = pthread_self();
13 pthread_id_np_t tid;
14 int rc;
15
16 pthread_getunique_np(&self, &tid);
17 printf("Thread 0x%.8x %.8x entered\n", tid);
18 errno = 0;
19 rc = sleep(30);
20 if (rc != 0 && errno == EINTR) {
21 printf("Thread 0x%.8x %.8x got a signal delivered to it\n",
22 tid);
23 return NULL;
24 }
25 printf("Thread 0x%.8x %.8x did not get expected results! rc=%d, errno=%d\n",
26 tid, rc, errno);
27 return NULL;
28 }
29
30 int main(int argc, char **argv)
31 {
32 int rc;
33 int i;
34 struct sigaction actions;
35 pthread_t threads[NUMTHREADS];
36
37 printf("Enter Testcase - %s\n", argv[0]);
38
39 printf("Set up the alarm handler for the process\n");
40 memset(&actions, 0, sizeof(actions));
41 sigemptyset(&actions.sa_mask);
42 actions.sa_flags = 0;
43 actions.sa_handler = sighand;
44
45 rc = sigaction(SIGALRM,&actions,NULL);
46 checkResults("sigaction\n", rc);
47
48 for(i=0; i<NUMTHREADS; ++i) {
49 rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
50 checkResults("pthread_create()\n", rc);
51 }
52
53 sleep(3);
54 for(i=0; i<NUMTHREADS; ++i) {
55 rc = pthread_kill(threads[i], SIGALRM);
56 checkResults("pthread_kill()\n", rc);
57 }
58
59 for(i=0; i<NUMTHREADS; ++i) {
60 rc = pthread_join(threads[i], NULL);
61 checkResults("pthread_join()\n", rc);
62 }
63 printf("Main completed\n");
64 return 0;
65 }
66
67 void sighand(int signo)
68 {
69 pthread_t self = pthread_self();
70 pthread_id_np_t tid;
71
72 pthread_getunique_np(&self, &tid);
73 printf("Thread 0x%.8x %.8x in signal handler\n",
74 tid);
75 return;
76 }
运行输出为:
1 Output:
2
3 Enter Testcase - QP0WTEST/TPKILL0
4 Set up the alarm handler for the process
5 Thread 0x00000000 0000000c entered
6 Thread 0x00000000 0000000d entered
7 Thread 0x00000000 0000000e entered
8 Thread 0x00000000 0000000c in signal handler
9 Thread 0x00000000 0000000c got a signal delivered to it
10 Thread 0x00000000 0000000d in signal handler
11 Thread 0x00000000 0000000d got a signal delivered to it
12 Thread 0x00000000 0000000e in signal handler
13 Thread 0x00000000 0000000e got a signal delivered to it
14 Main completed
我们可以通过截获的signal信号,来释放掉线程申请的资源,可是遗憾的是我们不能再signal处理里调用pthread_exit来终结掉线程,因为pthread_exit是中介当前线程,而signal被调用的方式可以理解为内核的回调,不是在同一个线程运行的,所以这里只能做处理释放资源的事情,线程内部只有判断有没有被中断(一般是EINTR)来断定是否要求自己结束,判定后可以调用pthread_exit退出。
此法对于一般的操作也是非常可行的,可是在有的情况下就不是一个比较好的方法了,比如我们有一些线程在处理网络IO事件,假设它是一种一个客户端对应一个服务器线程,阻塞从Socket中读消息的情况。我们一般在网络IO的库里面回家上对EINTR信号的处理,例如recv时发现返回值小于0,检查error后,会进行他对应的操作。有可能他会再recv一次,那就相当于我的线程根本就不回终止,因为网络IO的类有可能不知道在获取EINTR时要终止线程。也就是说这不是一个特别好的可移植方案,如果你线程里的操作使用了很多外来的不太熟悉的类,而且你并不是他对EINTR的处理手段是什么,这是你在使用这样的方法来终止就有可能出问题了。而且如果你不是特别熟悉这方面的话你会很苦恼,“为什么我的测试代码全是ok的,一加入你们部门开发的框架进来就不ok了,肯定是你们框架出问题了”。好了,为了不必要的麻烦,我最后没有使用这个方案。
pthread_cancel
这个方案是我最终采用的方案,我认为是解决这个问题,通用的最好的解决方案,虽然前面其他方案的有些问题他可能也不好解决,但是相比较而言,还是相当不错的
pthread_cancel可以单独使用,因为在很多系统函数里面本身就有很多的断点,当调用这些系统函数时就会命中其内部的断点来结束线程,如下面的代码中,即便注释掉我们自己设置的断点pthread_testcancel()程序还是一样的会被成功的cancel掉,因为printf函数内部有取消点(如果大家想了解更多的函数的取消点情况,可以阅读《Unix高级环境编程》的线程部分)
1 #include <pthread.h>
2 #include <stdio.h>
3 #include<stdlib.h>
4 #include <unistd.h>
5 void *threadfunc(void *parm)
6 {
7 printf("Entered secondary thread\n");
8 while (1) {
9 printf("Secondary thread is looping\n");
10 pthread_testcancel();11 sleep(1);
12 }
13 return NULL;
14 }
15
16 int main(int argc, char **argv)
17 {
18 pthread_t thread;
19 int rc=0;
20
21 printf("Entering testcase\n");
22
23 /* Create a thread using default attributes */
24 printf("Create thread using the NULL attributes\n");
25 rc = pthread_create(&thread, NULL, threadfunc, NULL);
26 checkResults("pthread_create(NULL)\n", rc);
27
28 /* sleep() is not a very robust way to wait for the thread */
29 sleep(1);
30
31 printf("Cancel the thread\n");
32 rc = pthread_cancel(thread);
33 checkResults("pthread_cancel()\n", rc);
34
35 /* sleep() is not a very robust way to wait for the thread */
36 sleep(10);
37 printf("Main completed\n");
38 return 0;
39 }
输出:
Entering testcase
Create thread using the NULL attributes
Entered secondary thread
Secondary thread is looping
Cancel the thread
Main completed
POSIX保证了绝大部分的系统调用函数内部有取消点,我们看到很多在cancel调用的情景下,recv和send函数最后都会设置pthread_testcancel()取消点,其实这不是那么有必要的,那么究竟什么时候该pthread_testcancel()出场呢?《Unix高级环境编程》也说了,当遇到大量的基础计算时(如科学计算),需要自己来设置取消点。
ok,得益于pthread_cancel,我们很轻松的把线程可以cancel掉,可是我们的资源呢?何时释放...
下面来看两个pthread函数
1.void pthread_cleanup_push(void (*routine)(void *), void *arg); 2.void pthread_cleanup_pop(int execute);
这两个函数能够保证在 1函数调用之后,2函数调用之前的任何形式的线程结束调用向pthread_cleanup_push注册的回调函数另外我们还可通过下面这个函数来设置一些状态
int pthread_setcanceltype(int type, int *oldtype);
Cancelability | Cancelability State | Cancelability Type |
disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_DEFERRED |
disabled | PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
deferred | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_DEFERRED |
asynchronous | PTHREAD_CANCEL_ENABLE | PTHREAD_CANCEL_ASYNCHRONOUS |
当我们设置type为PTHREAD_CANCEL_ASYNCHRONOUS时,线程并不会等待命中取消点才结束,而是立马结束
好了,下面贴代码:
1 #include <pthread.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <errno.h>
6 int footprint=0;
7 char *storage;
8 void freerc(void *s)
9 {
10 free(s);
11 puts("the free called");
12 }
13
14 static void checkResults(char *string, int rc) {
15 if (rc) {
16 printf("Error on : %s, rc=%d",
17 string, rc);
18 exit(EXIT_FAILURE);
19 }
20 return;
21 }
22
23 void *thread(void *arg) {
24 int rc=0, oldState=0;
25 rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); //close the cancel switch 26 checkResults("pthread_setcancelstate()\n", rc);
27 if ((storage = (char*) malloc(80)) == NULL) {
28 perror("malloc() failed");
29 exit(6);
30 }
31 rc = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldState); //open the cancel switch 32 checkResults("pthread_setcancelstate(2)\n", rc);
33 /* Plan to release storage even if thread doesn't exit normally */
34
35 pthread_cleanup_push(freerc, storage); /*the free is method here you can use your own method here*/
36
37 puts("thread has obtained storage and is waiting to be cancelled");
38 footprint++;
39 while (1)
40 {
41 pthread_testcancel(); //make a break point here 42 //pthread_exit(NULL); //test exit to exam whether the freerc method called 43 sleep(1);
44 }
45
46 pthread_cleanup_pop(1);
47 }
48
49 main() {
50 pthread_t thid;
51 void *status=NULL;
52
53 if (pthread_create(&thid, NULL, thread, NULL) != 0) {
54 perror("pthread_create() error");
55 exit(1);
56 }
57
58 while (footprint == 0)
59 sleep(1);
60
61 puts("IPT is cancelling thread");
62
63 if (pthread_cancel(thid) != 0) {
64 perror("pthread_cancel() error");
65 sleep(2);
66 exit(3);
67 }
68
69 if (pthread_join(thid, &status) != 0) {
70 if(status != PTHREAD_CANCELED){
71 perror("pthread_join() error");
72 exit(4);
73 }
74 }
75 if(status == PTHREAD_CANCELED)
76 puts("PTHREAD_CANCELED");
77
78 puts("main exit");
79 }
7 pthread_cancel用法及常见问题
2011-07-07 10:46 7985人阅读 评论(2) 收藏 举报
asynchronousnullthreadbufferlinuxjoin
先看下面一段程序:
[cpp] view plaincopy
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <unistd.h>
5
6 void* func(void *)
7 {
8 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //允许退出线程
9 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //设置立即取消
10 while (1)
11 {
12 ;
13 }
14 return NULL;
15 }
16
17 int main(int argc, char *argv[])
18 {
19 pthread_t thrd;
20 pthread_attr_t attr;
21 pthread_attr_init(&attr);
22 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
23
24 if ( pthread_create(&thrd, &attr, func, NULL) )
25 {
26 perror( "pthread_create error ");
27 exit(EXIT_FAILURE);
28 }
29
30 if ( !pthread_cancel(thrd) )
31 {
32 printf( "pthread_cancel OK\n " );
33 }
34
35 sleep( 10 );
36 return 0;
37 }
上面程序并不会将子线程取消,why?
这是因为子线程一直在while()循环,没有挂起,所以不能将其取消;那么该如何才能取消呢?
先来看一下pthread_cancel()的用法:
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
(1)什么是线程取消点
根据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();
(2)程序设计方面的考虑
如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
(3)与线程取消相关的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状态,如果是,则进行取消动作,否则直接返回
按照以上的描述是不是说在linux下,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用在linux下并不是取消点啊?我不明白为什么可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),就可以达到POSIX标准所要求的目标,我还是不太理解取消点的内涵,
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
加两个pthread_testcancel(); 是干什么的?特别是最后一个pthread_testcancel();按照上面的描述是不是线程运行到retcode = read(fd, buffer, length); 线程就终止了?那还要最后一个pthread_testcancel();做什么啊?
另外,查阅里相关资料,有人这样说:
首先,取消点并不是为了取消而存在的(pthread_testcancel除外),它们是等待其他信号的,比如pthread_cond_wait是等待某个条件的,sem_wait等待一个信号量。它们的共同点是“引起阻塞的系统调用”,在阻塞以后,除了等待的特定信号以外还可以处理一些公共信号,比如CANCEL信号。接收到特定信号后将恢复线程,而接收到CANCEL信号将退出线程(我理解为一个条件return)。
Read和Write在POSIX中是标准的取消点,因为它们也进行了“引起阻塞的系统调用”,但是因为Linux自身的问题,没有在这些函数中实现取消点,所以才需要添加pthread_testcancel来完成POSIX标准。按照你贴出的文字,C库函数都没有实现取消点,其他的pthread_开头的和wait结尾的应该都是实现了的。
至于后面也加pthread_testcancel,可能是进入和退出系统调用是两个独立的取消点吧,应对于I/O操作开始后但还未结束时的CANSEL信号,也许只是因为I/O比较费时间所以测试两次:)
还请达人来解惑,谢谢!
这篇关于二十九 多线程编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!