025 UNIX再学习 -- 线程

                                          
                                                                                   
                                                                                
                                           

终于要讲到线程部分,线程和进程让人够头痛的内容。

一、线程概念

老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境
首先需要了解下,什么是线程。
Linux 下的线程,可能我们比较陌生,但是我们一直在玩 Windows 系统。应用程序文件、任务管理器,这些东西应该是很溜的。比如:


查看详细信息,最上一列,右击选择列;找到线程打对勾。如下图,就可看到线程数了。





通过上图我们可以看到 一个进程中有多个线程。但是到底什么是线程呢?
参看:进程与线程的一个简单解释  
大神举得例子很有意思。
进程好似车间,线程好似车间工人;任务是有很多工人协同完成;车间内的空间设施工人都是共享的;有些房间比如厕所一次只能容纳一人,进入需加锁(互斥锁)。

进程,是资源分配单位。线程,是CPU调度基本单位。
线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务。
一个进程可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少要有一个主线程。


一个进程的所有线程都共享进程的代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户 ID 和组 ID 等。
一个进程的每个线程都拥有独立的 ID、寄存器值、栈内存、调度策略和优先级、信号掩码、errno变量以及线程私有数据等。
也可以说线程是包含在进程中的一种实体。它有自己的运行线索,可完成特定任务。可与其他线程共享进程中的共享变量及部分环境。可通过相互之间协同来完成进程所要完成的任务。
之前有转载一篇文章,可当扩展来看,参看:进程与线程及其区别

1.进程和线程的区别
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的
地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.

1.进程和线程的差别。
答:线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于
进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销
明显大于创建或撤消线程时的开销。


二、POSIX 线程

早期 UNIX 厂商各自提供私有的线程库版本,无论是接口还是实现,差异都非常大,代码移植非常困难。
我们将要讨论的线程接口来自 POSIX.1-2001。线程接口也称为“pthread”或“POSIX 线程”
POSIX 线程的功能测试宏是 _POSIX_THREADS。应用程序可以把这个宏用于 #ifdef 测试,从而在编译时确定是否支持线程,也可以把 _SC_THREADS 常数用于调用 sysconf 函数,进而在运行时确定是否支持线程。遵循 SUSv4 的系统定义符号 _POSIX_THREADS 的值为 200809L。
查看 /usr/include/i386-linux-gnu/bits/posix_opt.h 可以看到 _POSIX_THREADS 的定义

    
    
  1. 69 /* Tell we have POSIX threads.  */
  2. 70 #define _POSIX_THREADS  200809L
使用 pthread 需要包含一个头文件:pthread.h
同时连一个共享库:libpthread.so  即 gcc 编译时加选项 -lpthread

    
    
  1. #include <pthread.h>
  2. gcc ... -lpthread

三、线程标识

就像每个进程有一个进程 ID 一样,每个线程也有一个线程 ID。进程 ID 在整个系统中是唯一的,但线程 ID 不同,线程 ID 只有在它所属的进程上下文中才有意义。
线程 ID 是用 pthread_t 数据类型表示的.
查看 /usr/include/i386-linux-gnu/bits/pthreadtypes.h 可以看到 pthread_t 类型为无符号长整型
typedef unsigned long int pthread_t;
    
    
实现的时候可以用一个结构来代表 pthread_t 数据类型,所以可移植的操作系统实现不能把它作为整数处理。
因此必须使用一个函数来对两个线程 ID 进行比较。

1、函数 pthread_equal


    
    
  1. #include <pthread.h>
  2. int pthread_equal(pthread_t t1, pthread_t t2);
  3. 返回值:若相等,返回非 0 数值;否则,返回 0

(1)函数功能

比较线程 ID

(2)示例说明


    
    
  1. //示例一
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <pthread.h> 
  5.  
  6. int main()
  7.     pthread_t thread_id; 
  8.  
  9.     thread_id=pthread_self(); // 返回调用线程的线程ID 
  10.     printf( "Thread ID: %lu.\n",thread_id); 
  11.  
  12.     if (pthread_equal(thread_id,pthread_self())) 
  13.      { 
  14.         printf( "Equal!\n"); 
  15.      } else
  16.         printf( "Not equal!\n"); 
  17.     } 
  18.     return 0
  19. 编译: # gcc test.c -lpthread
  20. 输出结果:
  21. Thread ID: 3075704512.
  22. Equal!

    
    
  1. //示例二
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. void* routine1 (void* arg)
  8. {
  9.     pthread_t thread = pthread_self ();
  10.     printf ( "子线程1 ID:%lu\n", thread);
  11.    
  12.     return ( void*)thread;
  13. }
  14. void* routine2 (void* arg)
  15. {
  16.     pthread_t thread = pthread_self ();
  17.     printf ( "子线程2 ID:%lu\n", thread);
  18.    
  19.     if (pthread_equal (thread, ( pthread_t)arg))
  20.         printf ( "两个线程ID相同\n");
  21.     else
  22.         printf ( "两个线程ID不同\n");
  23.   
  24.     return ( void*)thread;
  25. }
  26. int main()
  27. {
  28.     pthread_t thread1;
  29.     int error = pthread_create (&thread1, NULL, routine1, NULL);
  30.     if (error)
  31.     {
  32.         perror ( "pthread_create");
  33.         exit (EXIT_FAILURE);
  34.     }
  35.    
  36.  pthread_join (thread1, NULL);
  37.     pthread_t thread2;
  38.     error = pthread_create (&thread2, NULL, routine2, ( void*)&thread1);
  39.     if (error)
  40.     {
  41.         perror ( "pthread_create");
  42.         exit (EXIT_FAILURE);
  43.     }
  44.    
  45.   //sleep (1);
  46.  pthread_join (thread2, NULL);
  47.     return 0;
  48. }
  49. 输出结果:
  50. 子线程 1 ID: 3076029248
  51. 子线程 2 ID: 3076029248
  52. 两个线程ID不同

(3)示例解析

pthread_equal 函数比较两个线程 ID,这个没什么可讲的。 
示例二是我讲完以后又添加的,创建了两个子线程。它们的线程 ID 不同,可以理解。
我要说的是 sleep 和 pthread_join 有什么区别呢? 还有为什么在线程中使用 usleep 不合适? 
留个疑问,后续我们讲多线程会一起讲到的!!

2、函数 pthread_self


    
    
  1. #include <pthread.h>
  2. pthread_t pthread_self( void);
  3. 返回值:调用线程的线程 ID

(1)函数功能

获取线程自身的 ID

(2)示例说明

当线程需要识别以线程 ID 作为标识的数据结构时,pthread_self 函数可以与 pthread_equal 函数一起使用, 如上例

    
    
  1. #include <pthread.h>
  2. #include <stdio.h>
  3. void* thread_func(void *arg)
  4. {
  5.     printf( "thread id=%lu\n", pthread_self());
  6.     return arg;
  7. }
  8. int main(void)
  9. {
  10.     pid_t pid;
  11.     pthread_t tid;
  12.     pid = getpid();
  13.     printf( "process id=%lu\n", pid);
  14.     pthread_create(&tid, NULL, thread_func, NULL);
  15.     pthread_join(tid, NULL);
  16.     return 0;
  17. }
  18. 输出结果:
  19. process id= 2933
  20. thread id= 3076057920

(3)示例解析

创建线程,查看线程自身的线程 ID

四、线程创建

1、函数 pthread_create


    
    
  1. #include <pthread.h>
  2. int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
  3.                           void *(*start_routine) (void *), void *arg);
  4. 返回值:若成功,返回 0;否则,返回错误编号

(1)函数功能

创建新线程

(2)参数解析

thread:输出线程 ID。pthread_t 即 unsigned long int。
attr:线程属性,NULL 表示缺省属性。pthread_attr_t 可能是整型也可能是结构体,因实现而异。
start_routine:线程过程函数指针。参数和返回值的类型都是 void*。启动线程过程其实就是调用一个函数,只不过是在一个独立的线程中调用,函数一旦返回,线程即告结束。
arg:传递给线程过程函数的参数。线程过程函数的调用者是系统内核,因此需要预先将参数存储到系统内核中。

(3)函数解析

main 函数可以被视为主线程的线程过程函数。main函数一旦返回,主线程即告结束。主线程一旦结束,进程即告结束。进程一旦结束,其所有的子线程统统结束。
应设法保证在线程过程函数执行期间,传递给它的参数 arg 所指向的目标持久有效。

注意,pthread 函数在调用失败时通常会返回错误码,它们并不像其他的 POSIX 函数一样设置 errno。每个线程都提供 errno 的副本,这只是为了与使用 errno 的现有函数兼容。子啊线程中,从函数中返回错误码更为清晰整洁,不需要依赖那些随着函数执行不断变化的全局状态,这样可以把错误的范围限制在引起出错的函数中。

(4)示例说明 


    
    
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>
  4. void *task (void* arg)
  5. {
  6.   printf ( "进入子线程\n");
  7.  sleep ( 5);
  8.   printf ( "子线程进程ID是:%lu\n", getpid ());
  9.   printf ( "22子线程ID是:%lu\n", pthread_self ());
  10.   printf ( "退出子线程\n");
  11. }
  12. int main (void)
  13. {
  14.   printf ( "主线程启动\n");
  15.   pthread_t thread;
  16.   int error = pthread_create (&thread, NULL, task, NULL);
  17.   if (error)
  18.   perror ( "pthread_create"), exit ( 1);
  19.  sleep ( 10);
  20.   printf ( "退出主线程\n");
  21.   printf( "11子线程ID是:%lu\n",thread);
  22.   printf ( "主线程进程ID是:%lu\n", getpid ());
  23.   printf( "主线程ID是:%lu\n",pthread_self());
  24.   return 0;
  25. }
  26. 编译: # gcc test.c -lpthread
  27. 输出结果:
  28. 主线程启动
  29. 进入子线程
  30. 子线程进程ID是: 3117
  31. 22子线程ID是: 3076180800
  32. 退出子线程
  33. 退出主线程
  34. 11子线程ID是: 3076180800
  35. 主线程进程ID是: 3117
  36. 主线程ID是: 3076183744

(5)示例解析

主线程需要等待子线程结束后再结束,如果不等待不如把上例的 sleep (10); 注释掉,则不执行子线程。
子线程和主线程是同一个进程,thread 为输出子线程 ID,pthread_self函数可以得到线程自身的线程ID
再有 pthread 函数在调用失败时通常会返回错误码。

五、线程的等待

1、函数 pthread_join


    
    
  1. #include <pthread.h>
  2. int pthread_join(pthread_t thread, void **retval);
  3. 返回值:成功返回 0,失败返回错误号

(1)函数功能

主要用于等待一个线程的结束,并且回去退出码

(2)参数解析

第一个参数:线程的 ID
第二个参数:二级指针,用于获取线程的退出码

(3)函数解析

该函数根据参数 thread 指定的线程进程等待,将目标线程终止时的退出状态信息拷贝到 *retval 这个参数指定的位置上。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是 joinable 的。

(4)示例说明


    
    
  1. //示例一
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. void *task (void *p)
  5. {
  6.   //ps 指向只读常量区 ps本身在栈区
  7.   char *ps = "hello";
  8.   return ps;
  9. }
  10. int main (void)
  11. {
  12.   //启动一个线程
  13.   pthread_t tid;
  14.  pthread_create (&tid, NULL, task, NULL);
  15.   //主线程等待子线程的处理,并且获取返回值
  16.   char *pc = NULL;
  17.   //pc 指针指向了 上面字符串的首地址
  18.  pthread_join (tid, ( void**)&pc);
  19.   printf ( "pc = %s\n", pc);
  20.   return 0;
  21. }
  22. 输出结果:
  23. pc = hello

    
    
  1. //示例二
  2. //使用pthread_join函数获取线程的返回值
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <pthread.h>
  7. void* task(void* p)
  8. {
  9.   int i = 0;
  10.   //静态局部变量,生命周期变长
  11.   static int sum = 0;
  12.   for(i = 1; i <= 100; i++)
  13.  {
  14.   sum += i;
  15.  }
  16.   return ( void*)∑
  17. }
  18. int main( void)
  19. {
  20.   //1.启动一个线程
  21.   pthread_t tid;
  22.  pthread_create(&tid, NULL,task, NULL);
  23.   //2.主线程进行等待,获取返回值
  24.   int* pi = NULL;
  25.  pthread_join(tid,( void**)&pi);
  26.   printf( "子线程返回的数据是:%d\n",*pi);
  27.   return 0;
  28. }
  29. 输出结果:
  30. 子线程返回的数据是: 5050

(5)示例解析

上例中主要需要注意的是 pthread_join 获取返回值。
从线程过程函数中返回值的方法:
线程过程函数将所需返回的内容放在一块内存中,返回该内存的地址,同时保证这块内存,在函数返回即线程结束以后依然有效。
若 retval 参数非 NULL,则 pthread_join 函数将线程过程函数所返回的指针,拷贝到该参数所指向的内存中。
若线程过程函数所返回的指针指向动态分配的内存,则还需保证在用过该内存之后释放它。

2、函数 pthread_detach


    
    
  1. #include <pthread.h>
  2. int pthread_detach(pthread_t thread);
  3. 返回值:成功返回 0;失败返回一个错误码

(1)函数功能

主要用于将参数指定的线程标记为分离状态,对于分离状态的线程来说:当该线程终止后,会自动将资源释放给系统,不需要其他线程的加入/等待,也就是说分离的线程无法被其他线程使用 pthread_join 进行等待
建议:对于新启动的线程来说,要么使用 pthread_detach 设置为分离状态,要么使用 pthread_join 设置为可加状态。

(2)示例说明


    
    
  1. #include <stdio.h>
  2. #include <pthread.h>
  3. void *task (void *p)
  4. {
  5.   int i = 0;
  6.   for (i = 0; i <= 10; i++)
  7.    printf ( "子线程中:i = %d\n", i);
  8. }
  9. int main (void)
  10. {
  11.   //启动一个子线程,打印1~10之间的数
  12.   pthread_t tid;
  13.  pthread_create (&tid, NULL, task, NULL);
  14.   //设置子线程为分离的状态
  15.  pthread_detach (tid);
  16.   //主线程进行等待,然后打印1~10之间的数
  17.  pthread_join (tid, NULL);
  18.   int i = 0;
  19.   for (i = 1; i <= 10; i++)
  20.    printf ( "主线程中:i = %d\n", i);
  21.   return 0;
  22. }
  23. 输出结果:
  24. 主线程中:i = 1
  25. 主线程中:i = 2
  26. 主线程中:i = 3
  27. 主线程中:i = 4
  28. 主线程中:i = 5
  29. 主线程中:i = 6
  30. 主线程中:i = 7
  31. 主线程中:i = 8
  32. 主线程中:i = 9
  33. 主线程中:i = 10

(3)示例解析

我们上面有讲到,如果使用 pthread_join 等待,则等待子线程完成后打印主线程1~10之间的数。
但是使用了 pthread_detach 分离,则直接打印主线程1~10之间的数,且 pthread_join 等待也会失效。

六、线程终止

如果进程中的任意线程调用了 exit、_Exit 或者 _exit,那么整个进程就会终止。与此相比类似,如果默认的动作是终止进程,那么发送到线程的信号就会终止整个进程。
单个线程可以通过 3 种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
(1)线程可以简单地从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消。
(3)线程调用 pthread_exit。

1、我们讲一下第三种调用 pthread_exit。


    
    
  1. #include <pthread.h>
  2. void pthread_exit(void *retval);

(1)函数功能

主要用于终止正在运行的线程通过参数 retval 来带出线程的退出状态信息
在同一个进程中的其他线程可以通过调用 pthread_join 函数来获取退出状态信息

(2)函数解析

在线程过程函数或者被线程过程函数直接或间接调用的函数中,调用 pthread_exit 函数,其效果都与在线程过程函数中执行 return 语句效果一样 -- 终止调用线程。
注意,在任何线程中调用 exit 函数,被终止的都是进程。当然随着进程的终止,隶属于该进程的包括调用线程在内的所有线程也都一并终止。

(3)示例说明


    
    
  1. //示例一
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. #include <stdlib.h>
  5. void *task (void *p)
  6. {
  7.   int i = 0;
  8.      for (i = 1; i < 100; i++)
  9.  {
  10.    if (i == 10)
  11.   {
  12.     //return (void*)i;
  13.    pthread_exit (( void*)i);
  14.     //exit (100);
  15.   }
  16.    printf ( "子线程中:i = %d\n", i);
  17.  } 
  18. }
  19. int main (void)
  20. {
  21.   pthread_t tid;
  22.  pthread_create (&tid, NULL, task, NULL);
  23.   int res = 0;
  24.  pthread_join (tid, ( void**)&res);
  25.   printf ( "res = %d\n", res);
  26.   return 0;
  27. }
  28. 编译: # gcc test.c -lpthread
  29. 输出结果:
  30. 子线程中:i = 1
  31. 子线程中:i = 2
  32. 子线程中:i = 3
  33. 子线程中:i = 4
  34. 子线程中:i = 5
  35. 子线程中:i = 6
  36. 子线程中:i = 7
  37. 子线程中:i = 8
  38. 子线程中:i = 9
  39. res = 10

    
    
  1. //示例二
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. void circle_area (double r)
  8. {
  9.     double* s = malloc ( sizeof ( double));
  10.     *s = 3.14 * r * r;
  11.    
  12.     pthread_exit (s);
  13. }
  14. void* start_routine (void* arg)
  15. {
  16.     circle_area (*( double*)arg);
  17.     printf( "此句将不被执行");
  18.     return NULL;
  19. }
  20. int main()
  21. {
  22.     double r = 10.0;
  23.     pthread_t thread;
  24.     int error = pthread_create (&thread, NULL, start_routine, ( void*)&r);
  25.     if (error)
  26.     {
  27.         perror ( "pthread_create");
  28.         exit (EXIT_FAILURE);
  29.     }
  30.    
  31.     double* s;
  32.     if (pthread_join (thread, ( void**)&s))
  33.     {
  34.         perror ( "pthread_join");
  35.         exit (EXIT_FAILURE);
  36.     }
  37.    
  38.     printf ( "圆面积:%g\n", *s);
  39.     free (s);
  40.     return 0;
  41. }
  42. 输出结果:
  43. 圆面积: 314

(4)示例解析

该示例说明了,在任何线程中调用 exit 函数,被终止的都是进程。进程终止可其他线程就就同样终止了。
使用 pthread_join 获取退出状态信息,return 可以返回退出状态信息,pthread_exit 同样也可以返回退出状态信息。

七、线程取消

1、函数 pthread_cancel


    
    
  1. #include <pthread.h>
  2. int pthread_cancel(pthread_t thread);
  3. 返回值:成功返回 0;失败返回错误码

(1)函数功能

主要用于对参数指定的线程发送取消的请求

(2)函数解析

该函数只是向线程发出取消请求,并不等于线程终止。缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。在取消点处,线程检查其自身是否已被取消,若是则立即终止。
当线程调用一些特定函数时,取消点会出现。

2、函数 pthread_setcancelstate


    
    
  1. #include <pthread.h>
  2. int pthread_setcancelstate(int state, int *oldstate);
  3. 返回值:成功返回 0,失败返回错误码

(1)函数功能

主要用于设置新的取消状态,返回之前的取消状态

(2)参数解析

state:取消状态,可取以下值
    PTHREAD_CANCEL_ENABLE  接受取消请求(缺省)
    PTHREAD_CANCEL_DISABLE  忽略取消请求
oldstate:输出原取消状态,可取 NULL

3、函数 pthread_setcanceltype


    
    
  1. #include <pthread.h>
  2. int pthread_setcanceltype(int type, int *oldtype);
  3. 返回值:成功返回 0,;失败返回错误码

(1)函数功能

主要用于设置新的取消类型,获取之前的取消类型

(2)参数解析

type:取消类型,可取以下值
    PTHREAD_CANCEL_DEFERRED  延迟取消(缺省)
        被取消线程接收到取消请求之后并不立即终止,而是一直等到执行了特定的函数(取消点)之后再终止。
    PTHREAD_CANCEL_ASYNCHRONOUS  异步取消
        被取消线程可以在任意时刻终止,而不是非得遇到取消点。
oldtype:输出原取消类型,可取 NULL

4、示例说明


    
    
  1. //线程取消函数的使用
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <pthread.h>
  6. void* task(void* p)
  7. {
  8.   //设置允许被取消
  9.  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  10.   //设置为立即取消
  11.  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  12.   while( 1)
  13.  {
  14.    printf( "I am superman!\n");
  15.   sleep( 1);
  16.  }
  17. }
  18. void* task2(void* p)
  19. {
  20.   printf( "开始取消线程...\n");
  21.  sleep( 5);
  22.   printf( "取消线程结束\n");
  23.  pthread_cancel(*( pthread_t*)p);
  24. }
  25. int main(void)
  26. {
  27.   //1.启动一个新线程,不断进行打印
  28.   pthread_t tid;
  29.  pthread_create(&tid, NULL,task, NULL);
  30.   //2.启动另外一个新线程,负责取消上述线程
  31.   pthread_t tid2;
  32.  pthread_create(&tid2, NULL,task2,&tid);
  33.   //3.主线程等待子线程的结束
  34.  pthread_join(tid, NULL);
  35.  pthread_join(tid2, NULL);
  36.   return 0;
  37. }
  38. 编译: # gcc test.c -lpthread
  39. 输出结果:
  40. 开始取消线程...
  41. I am superman!
  42. I am superman!
  43. I am superman!
  44. I am superman!
  45. I am superman!
  46. 取消线程结束

5、示例解析

创建了两个线程,线程 2 负责取消线程 1。而设置取消状态为允许被取消,取消类型是立即取消。

八、线程清理处理程序

1、函数 pthread_cleanup_push、pthread_cleanup_pop


    
    
  1. #include <pthread.h>
  2. void pthread_cleanup_push(void (*routine)(void *), void *arg);
  3. void pthread_cleanup_pop(int execute);

(1)函数解析

与进程在退出时可用 atexit 函数安排退出时类似的,线程也可以安排它退出时需要调用的函数。这样的函数就称为 线程清理处理程序。一个线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。

(2)参数解析

当线程执行以下动作时,清理函数 routine 是由 pthread_cleanup_push 函数调度的,调用时只有一个参数 arg:
调用 pthread_exit 时;
响应取消请求时;
用非零 execute 参数设置 0,清理函数将不被调用。
不管发生上述哪种情况,pthread_cleanup_pop 都将删除 pthread_cleanup_push 调用建立的清理处理程序。

(3)示例说明


    
    
  1. #include "apue.h"
  2. #include <pthread.h>
  3. void
  4. cleanup (void *arg)
  5. {
  6.   printf( "cleanup: %s\n", ( char *)arg);
  7. }
  8. void *
  9. thr_fn1 (void *arg)
  10. {
  11.   printf( "thread 1 start\n");
  12.  pthread_cleanup_push(cleanup, "thread 1 first handler");
  13.  pthread_cleanup_push(cleanup, "thread 1 second handler");
  14.   printf( "thread 1 push complete\n");
  15.   if (arg)
  16.    return(( void *) 1);
  17.  pthread_cleanup_pop( 0);
  18.  pthread_cleanup_pop( 0);
  19.   return(( void *) 1);
  20. }
  21. void *
  22. thr_fn2 (void *arg)
  23. {
  24.   printf( "thread 2 start\n");
  25.  pthread_cleanup_push(cleanup, "thread 2 first handler");
  26.  pthread_cleanup_push(cleanup, "thread 2 second handler");
  27.   printf( "thread 2 push complete\n");
  28.   if (arg)
  29.   pthread_exit(( void *) 2);
  30.  pthread_cleanup_pop( 0);
  31.  pthread_cleanup_pop( 0);
  32.  pthread_exit(( void *) 2);
  33. }
  34. int
  35. main (void)
  36. {
  37.   int   err;
  38.   pthread_t tid1, tid2;
  39.   void  *tret;
  40.  err = pthread_create(&tid1, NULL, thr_fn1, ( void *) 1);
  41.   if (err != 0)
  42.   err_exit(err, "can't create thread 1");
  43.  err = pthread_create(&tid2, NULL, thr_fn2, ( void *) 1);
  44.   if (err != 0)
  45.   err_exit(err, "can't create thread 2");
  46.  err = pthread_join(tid1, &tret);
  47.   if (err != 0)
  48.   err_exit(err, "can't join with thread 1");
  49.   printf( "thread 1 exit code %ld\n", ( long)tret);
  50.  err = pthread_join(tid2, &tret);
  51.   if (err != 0)
  52.   err_exit(err, "can't join with thread 2");
  53.   printf( "thread 2 exit code %ld\n", ( long)tret);
  54.   exit( 0);
  55. }
  56. 编译: # gcc test.c -lpthread
  57. 输出结果:
  58. thread 2 start
  59. thread 2 push complete
  60. cleanup: thread 2 second handler
  61. cleanup: thread 2 first handler
  62. thread 1 start
  63. thread 1 push complete
  64. thread 1 exit code 1
  65. thread 2 exit code 2

(4)示例解析

两个线程都正确的启动和退出了,但是只有第二个线程的清理处理程序被调用了,而区别是一个是 return 返回,一个为调用 pthread_exit。因此,可以看出如果线程是通过从它的启动例程中返回而终止的话,它的清理处理程序就不会被调用。还要注意,清理处理程序时按照与它们安装时相反的顺序被调用的。
                                   
                                   
               
                   
  •                                                
  •                                                     点赞                         2                        
  •                        
  •                                                     收藏
  •                        
  •                                                     分享
  •                                                                                                                        
  •                                                        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值