线程的操作
线程标识
线程的ID表示数据类型:pthread_t (内核中的实现是unsigned long/unsigned int/指向pthread结构的指针(不可移植)几种类型)
1.对两个线程ID进行比较
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread tid2); //返回值:若相等则返回非0值,不相等返回0
2.获取自身的线程id
#include <pthread.h> pthread_t pthread_self(void);
线程创建
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); //thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。 //attr: 用于指定线程的属性 //start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。 //arg: 传递给线程函数的参数。
编程模型
...
pthread_t ntid;
... void *thr_fn(void *arg) {...} ... int main() { ... int err; ... err=pthread_create(&ntid, NULL, thr_fn, NULL); //此处的参数根据具体需要会改变 if(err!=0) //注意不要忘记检查线程创建是否成功 ...; ... }
线程终止
单个线程的三种退出方式:
1)通过return从线程函数返回;
2)通过调用pthread_exit()函数使线程退出;
3)被同一进程其他线程取消。
需要注意的地方:一是,主线程中如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时所有的其他线程也将终止。二是,如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,其他线程也不会结束,直到所有的线程都结束时,进程才结束。三: 当一个线程调用pthread_exit函数退出,或简单从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。
pthread_create和pthread_exit函数的无类型指针参数能传递的数值不止一个,该指针可以传递包含更复杂信息的结构的地址,但是注意这个结构所使用的内存在调用者完成调用以后必须仍然是有效的,否则会出现无效或非法内存访问。例如,在调用线程的栈上分配了该结构,那么其他的线程使用这个结构时内存内容可能已经改变了。又如线程在自己的栈上分配了一个结构然后把指向这个结构的指针传给pthread_exit,那么当调用pthread_join的线程试图使用这个结构时,这个栈可能已经被撤销,这块内存也以另作他用。
1 /* 2 当调用pthread_exit后,当前线程操作的结构可能会发生改变,但是仍然能够被其他线程所访问。 3 */ 4 5 #include <pthread.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 9 struct foo 10 { 11 int a, b, c, d; 12 }; 13 14 void printfoo(const char* s, const struct foo *fp) 15 { 16 printf(s); 17 printf(" structure at 0x%x\n", (unsigned)fp); 18 printf(" foo.a=%d\n", fp->a); 19 printf(" foo.b=%d\n", fp->b); 20 printf(" foo.c=%d\n", fp->c); 21 printf(" foo.d=%d\n", fp->d); 22 } 23 24 void *thr_fn1(void *arg) 25 { 26 struct foo foo={1,2,3,4}; 27 printfoo("thread 1:\n", &foo); 28 pthread_exit((void*)&foo); 29 } 30 31 void *thr_fn2(void *arg) 32 { 33 printf("thread 2: ID is %d\n", pthread_self()); 34 pthread_exit((void*)0); 35 } 36 37 int main(void) 38 { 39 int err; 40 pthread_t tid1, tid2; 41 struct foo *fp; 42 43 err=pthread_create(&tid1, NULL, thr_fn1, NULL); 44 if (err!=0) 45 printf("can‘t create thread 1: %s\n", strerror(err)); 46 err=pthread_join(tid1, (void*)&fp); 47 if (err!=0) 48 printf("can‘t join with thread 1: %s\n", strerror(err)); 49 50 sleep(1); 51 printf("parent starting second thread\n"); 52 53 err=pthread_create(&tid2, NULL, thr_fn2, NULL); 54 if (err!=0) 55 printf("can‘t create thread 2: %s\n", strerror(err)); 56 57 sleep(1); 58 printfoo("parent: \n", fp); 59 exit(0); 60 }
1.线程取消相关函数
1)int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。还有信号处理的过程,处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
#include <pthread.h> int pthread_cancel(pthread_t tid); //成功返回0,失败放回错误编号(并不会设置errno)
2)int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,
分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。
3)int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED(默认)和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入原来的取消动作类型值。
4)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标准指定了几个取消点,其中包括:
man 7 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()调用.
取消一个线程的过程:
1) 其他线程通过调用pthread_cancel()函数,向目标线程发送取消请求(cancellation request)。
2) 取消请求发出后,根据目标线程的cancel state来决定取消请求是否会到达目标线程:
a. 如果目标线程的cancel state是PTHREAD_CANCEL_ENABLE(默认),取消请求会到达目标线程。
b. 如果目标线程的cancel state是PTHREAD_CANCEL_DISABLE,取消请求会被放入队列。直到目标线程的cancel state变为PTHREAD_CANCEL_ENABLE,取消请求才会从队列里取出,发到目标线程。
3) 取消请求到达目标线程后,根据目标线程的cancel type来决定线程何时取消:
a. 如果目标线程的cancel type是PTHREAD_CANCEL_DEFERRED(默认),目标线程并不会马上取消,而是在执行下一条cancellation point的时候才会取消。有很多系统函数都是cancellation point,详细的列表可以在Linux上用man 7 pthreads查看。除了列出来的cancellation point,pthread_testcancel()也是一个cancellation point。就是说目标线程执行到pthread_testcancel()函数的时候,如果该线程收到过取消请求,而且它的cancel type是PTHREAD_CANCEL_DEFERRED,那么这个线程就会在这个函数里取消(退出),这个函数就不再返回了,目标线程也没有了。
b. 如果目标线程的cancel type是PTHREAD_CANCEL_ASYNCHRONOUS,目标线程会立即取消(这里的“立即”只是说目标线程不用等执行到属于cancellation point的函数的时候才会取消,它会在获得调度之后立即取消,因为内核调度会有延时,所以并不能保证时间上的“立即”)。
1 void thread_function(void *arg) 2 { 3 /** 4 * 线程准备执行一些关键工作,在这个过程中不希望被取消。 5 * 所以先通过pthread_setcancelstate()将本线程的cancel state 6 * 设为disabled。 7 */ 8 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 9 /* 执行关键工作 */ 10 ... 11 /** 12 * 关键工作执行完成,可以被取消。 13 * 通过pthread_setcancelstate()将本线程的cancel state 14 * 设为enabled。 15 */ 16 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); 17 /** 18 * 调用pthread_testcancel()函数,检查一下在cancel state 19 * 为disabled状态的时候,是否有取消请求发送给本线程。 20 * 如果有的话就取消(退出)。 21 */ 22 pthread_testcancel(); 23 /** 24 * pthread_testcancel()返回了,表明之前没有取消请求发送给本线程, 25 * 继续其余的工作。 26 * 这时候如果有取消请求发送给本线程,会在下一次执行到 27 * cancellation point的时候(例如sleep(), read(), write(), ...)时取消。 28 */ 29 ... 30 /** 31 * 从这里开始,函数里不再包含cancellation point了。 32 * 如果收到取消请求,将无法取消。所以先把本线程的cancel type 33 * 设为asynchronous,收到取消请求将立即取消。 34 */ 35 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); 36 /* 不包含cancellation point的代码 */ 37 ... 38 }
1 /* 2 * pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state); 3 * 其中将cancel状态改为PTHREAD_CANCEL_DISABLE,将原来的状态保存至old_cancel_state变量; 4 * pthread_cancel(tid); //发送终止信号 5 * pthread_setcancelstate(old_cancel_state, NULL); 恢复cancel状态 6 */ 7 #include <pthread.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <unistd.h> 11 12 /*这个线程中本要睡10秒,5秒时发cancel信号后立即退出*/ 13 void* thread1(void* data) 14 { 15 int i=0; 16 int oldtype; 17 18 //pthread_setcanceltype(PTHREAD_CANCEL_DEFFERED, &oldtype); 这一句是默认的 19 printf("thread1 start......\n"); 20 while(i<10) 21 { 22 printf("thread1 running...\n"); 23 sleep(1); //sleep()是取消点,所以读5秒时会从这里退出 24 i++; 25 } 26 27 printf("thread1 exit...\n"); //while内收到cancel信号的话,此句不会执行 28 pthread_exit(NULL); 29 } 30 void* thread2(void* data) 31 { 32 int i=0; 33 int old_cancel_state; 34 35 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state); 36 printf("thread2 start......\n"); 37 while(i<10) 38 { 39 printf("thread2 running...\n"); 40 sleep(1); 41 i++; 42 } 43 44 printf("thread2 exit...\n"); 45 pthread_setcancelstate(old_cancel_state, NULL); 46 pthread_exit(NULL); 47 } 48 49 int main() 50 { 51 pthread_t tid1, tid2; 52 53 if (pthread_create(&tid1, NULL, thread1, NULL) != 0) 54 { 55 exit(1); 56 } 57 58 sleep(5); // sleep a while. 59 pthread_cancel(tid1); //发送终止信号 60 61 62 63 if (pthread_create(&tid2, NULL, thread2, NULL) != 0) 64 { 65 exit(1); 66 } 67 sleep(5); 68 pthread_cancel(tid2); 69 70 pthread_join(tid1, NULL); 71 pthread_join(tid2, NULL); 72 printf("main thread exit...\n"); 73 74 return 0; 75 }
思考:
1.线程取消的作用,在什么时候会使用线程取消?
1)共享资源的原子操作,一段时间内只允许一个线程对共享资源的访问,其余线程全部pthread_cancel();同时还要使用pthread_setcancelstate()对此线程进行保护。
2. 某某函数是 Cancellation Points,这种方法是容易令人混淆的,其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。怎么理解?
线程终止的清理工作
线程终止后保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
在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为执行;这个参数并不影响异常终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \ { struct _pthread_cleanup_buffer _buffer; \ _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) \ _pthread_cleanup_pop (&_buffer, (execute)); }
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
1 pthread_cleanup_push(pthread_mutex_unlock, (void*) &mut); 2 pthread_mutex_lock(&mut); 3 /* do some work */ 4 pthread_mutex_unlock(&mut); 5 pthread_cleanup_pop(0); 6 或者 7 void cleanup(void *arg) 8 { 9 pthread_mutex_unlock(&mutex); 10 } 11 12 void* thread0(void* arg) 13 { 14 pthread_cleanup_push(cleanup, NULL); // thread cleanup handler p 15 thread_mutex_lock(&mutex); 16 pthread_cond_wait(&cond, &mutex); 17 pthread_mutex_unlock(&mutex); 18 pthread_cleanup_pop(0); 19 pthread_exit(NULL); 20 }
部分参考:http://www.cnblogs.com/lijunamneg/archive/2013/01/25/2877211.html
原文:http://www.cnblogs.com/kwseeker-bolgs/p/4567225.html