线程的深度剖析

在来认识线程是什么?

  • 我们在熟悉进程的基础之上呢,那我们首先建立一个概念,线程的本质仍然是一个线程,我们给他起一个名字,叫做轻量级进程(light weight process)
  • 那么进程具有自己的pcb,也就是存放自己属性的结构体,同样的,线程也有,只不过是线程的pcb存放的东西比较少,最大的区别就是线程没有独立的地址空间,一个进程中的所有进程是共享地址空间的。(两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。)
  • 由此引出进程和线程的不同叫法。在linux下,线程是最小的执行单元进程是最小分配资源的单元,那么我们就可以把进程看做是只有一个线程的进程。

线程共享资源

1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户ID和组ID
5.内存地址空间 (.text/.data/.bss/heap/共享库)

线程非共享资源

1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级

线程优、缺点

优点:1. 提高程序并发性	2. 开销小	3. 数据通信、共享数据方便

缺点:
1.线程用到的函数是库函数,不稳定	
2. 调试、编写困难、gdb不支持线程的调试	
3. 对信号支持不好
  • 优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。linux下对于线程和进程的实现调用的底层函数大部分是一样的,因此差别不大。

获取线程ID的函数

  • pthread_self函数,获取线程ID。其作用对应进程中 getpid() 函数。
	   #include <pthread.h>
       pthread_t pthread_self(void);
DESCRIPTION 
       The  pthread_self()  function returns the ID of the calling thread.  This is the same value that is returned in *thread in the pthread_create(3) call that created this thread.
描述:这个函数返回调用线程的ID,函数返回值是pthread_create这个函数创建出来的线程	
RETURN VALUE 返回值
       This function always succeeds, returning the calling thread’s ID.
       这个函数总是成功的,返回调用线程的ID
ERRORS 错误
       This function always succeeds.
		这个函数不会出错
  • 线程ID:pthread_t类型,本质上:在Linux下为无符号整数(%lu),
  • 线程ID是进程内部识别线程的标志。(两个进程间,线程ID允许相同)
  • 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。

创建线程的函数

  • pthread_create函数,创建一个新线程。他的作用,对应进程中fork() 函数。
SYNOPSIS
       #include <pthread.h>
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  void *(*start_routine) (void *), void *arg);
DESCRIPTION 描述
       The  pthread_create()  function  starts  a  new  thread  in  the calling process.  The new thread starts execution by invoking
       start_routine(); arg is passed as the sole argument of start_routine().
       The new thread terminates in one of the following ways:
       * It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls
         pthread_join(3).
       * It returns from start_routine().  This is equivalent to calling pthread_exit(3) with the value supplied in the return state-ment.
       * It is canceled (see pthread_cancel(3)).
     * Any of the threads in the process calls exit(3), or the main thread performs a return from main().  This causes the termina-tion of all threads in the process.
       The attr argument points to a pthread_attr_t structure whose contents are used at thread creation time to determine attributes
       for the new thread; this structure is initialized using pthread_attr_init(3) and related functions.  If attr is NULL, then the
       thread is created with default attributes.
       Before  returning,  a  successful call to pthread_create() stores the ID of the new thread in the buffer pointed to by thread;
       this identifier is used to refer to the thread in subsequent calls to other pthreads functions.
       The new thread inherits a copy of the creating thread’s signal mask (pthread_sigmask(3)).  The set of pending signals for  the
       new  thread  is  empty (sigpending(2)).  The new thread does not inherit the creating thread’s alternate signal stack (sigalt-stack(2)).
       The new thread inherits the calling thread’s floating-point environment (fenv(3)).
       The initial value of the new thread’s CPU-time clock is 0 (see pthread_getcpuclockid(3)).
       
描述:
       pthread_create ( )函数在调用过程中启动一个新线程。新线程通过调用
       start _ routine ( );arg作为start_routine ( )的唯一参数传递。

新线程以下列方式之一终止:
		*它调用pthread_exit(3 ),指定一个退出状态值,该值可用于调用同一进程中的另一个线程通过 pthread _ join ( 3 )。
		
*它从start _ routine ( )返回。这相当于用返回状态下提供的值调用 pthread_exit。

*它被取消(见pthread _ cancel ( 3 ) )。

*进程中的任何线程调用exit(3 ),或者主线程执行main ( )返回。这导致了终结进程中所有线程的操作。

attr参数指向pthread_attr_t结构,其内容在线程创建时用于确定属性用于新线程;使用pthread _ attr _ init ( 3 )和相关函数初始化该结构。如果attr为空,则线程是用默认属性创建的。

返回之前,对pthread_create ( )的成功调用将新线程的ID存储在线程指向的缓冲区中;
该标识符用于在后续调用其他pthreads函数时引用线程。

新线程继承了创建线程的信号掩码的副本( pthread _ sigmask ( 3 ) )。的挂起信号集
新线程为空( sigpending ( 2 ) )。新线程不继承创建线程的备用信号堆栈( sigalt -
堆栈( 2 ) )。

新线程继承调用线程的浮点环境( fenv(3 ) )。

新线程的CPU时间时钟的初始值为0 (参见pthread _ getcpuclock id ( 3 ) )。

   Linux-specific details
       The new thread inherits copies of the calling thread’s capability sets  (see  capabilities(7))  and  CPU  affinity  mask  (see
       sched_setaffinity(2)).
linux下特殊的信息      
		新线程继承调用线程的能力集(参见能力( 7 ) )和CPU相似性掩码(参见
sched _ setaffinity ( 2 ) )。

RETURN VALUE 返回值
       On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
		成功返回0,在失败的情况下,返回错误编号,这个编号对应描述线程失败的信息
  • 上面的linux下man手册的描述,我做的一些简单的翻译,我在用我自己的话来讲讲。
  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 参数:
    pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
  • 参数1:传出参数,保存系统为我们分配好的线程ID
  • 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
  • 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
  • 参数4:线程主函数执行期间所使用的参数。
  • 在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态,稍后详细介绍pthread_join。
  • pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self(3)可以获得当前线程的id。
  • attr参数表示线程属性,现在先不讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值,感兴趣的读者可以参考APUE。
来看代码
  1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 
  6 void* pth_func(void* arg)
  7 {
  8     printf("new thread id : %lu,process id :%lu\n",pthread_self(),getpid());
  9     return ((void*)111);
 10 }
 11 
 12 int main()
 13 {
 14     pthread_t tid;
 15     int ret;
 16     ret = pthread_create(&tid,NULL,pth_func,NULL);
 17     if(ret != 0)
 18     {
 19         fprintf(stderr,"pthread_create error:%s",strerror(ret));
 20         exit(0);
 21     }
 22     sleep(2);
 23     printf("old thread id :%lu, process id : %lu\n",pthread_self(),getpid());
 24     return 0;
 25 }

在这里插入图片描述

  • 我们可以看到新创建出来的线程和创建新线程的线程两个都在进程3226中,但是两个线程他们彼此的线程ID不一样。
  • 上面的代码中我使用了strerror这个函数,是由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误码转换成错误信息再打印。
  • 如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时2秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行。那有没有更好的办法,当然有,下面我将用到。
循环创建多个线程,每个线程打印自己是第几个被创建的线程。(类似于进程循环创建子进程)
  1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<string.h>
  4 #include<stdlib.h>
  5 
  6 void* pth_func(void* arg)
  7 {
  8     int i = (int)arg;
  9     printf("The %dth pthread id : %lu\n",i,pthread_self());
 10     return ((void*)11);
 11 }
 12 
 13 int main()
 14 {
 15     pthread_t tid;
 16     int i,ret;
 17 
 18     for(i = 1;i <= 5; ++i)
 19     {
 20         ret = pthread_create(&tid,NULL,pth_func,(void*)i);
 21         if(ret != 0)
 22         {
 23             fprintf(stderr,"pthread create error:%lu\n",strerror(ret));
 24             exit(0);
 25         }
 26         sleep(1);
 27     }
 28     sleep(2);
 29     printf("old pthread id : %lu\n",pthread_self());
 30     return 0;
 31 }

在这里插入图片描述

  • 程序中有几个需要注意的地方,26行中的哪个sleep函数是必须的,否则新创建的线程会竞争系统资源,同时由于系统的调度不可知,因此会造成打印出来是乱序的。
  • 还有读者可以看到8行我是直接将void* 类型的arg直接转换成int类型的i,20行我是直接将int类型的i强制转换成了void*类型的。如果将pthread_create函数参4修改为(void )&i, 将线程主函数内改为 i=((int *)arg) 是否可以?
  • 答案是不确定的,但是我们不建议你这样写,为什么呢?因为一个进程中的所有线程是共享资源的,因此系统在资源调度时,有可能会出现一号线程拿资源的时候,2号线程已经改了内存中的值了,所以就会出错。
线程间共享全局变量!
  • 注意:线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助mmap
  1 #include<pthread.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 int global = 100;
  5 
  6 void* pth_func(void* arg)
  7 {
  8     global = 200;
  9     printf("new thread id : %lu, global : %d\n",pthread_self(),global);
 10     return NULL;
 11 }
 12 
 13 int main()
 14 {
 15     pthread_t tid;
 16     int ret;
 17 
 18     ret = pthread_create(&tid,NULL,pth_func,NULL);
 19     if(ret != 0)
 20     {
 21         fprintf(stderr,"pthread create error:%lu\n",strerror(ret));
 22         exit(1);
 23     }
 24     sleep(1);
 25     printf("old thread id :%lu,global :%d\n",pthread_self(),global);
 26     return 0;
 27 }

在这里插入图片描述

  • 我们可以看到在新线程中改掉了全局变量global的值,老线程中的全局变量的值global也被改变了。因此说明线程共享地址空间,重点是全局变量。
线程的退出
SYNOPSIS
       #include <pthread.h>

       void pthread_exit(void *retval);

DESCRIPTION
       The  pthread_exit() function terminates the calling thread and returns a value via retval that (if the thread is
       joinable) is available to another thread in the same process that calls pthread_join(3).

       Any clean-up handlers established by pthread_cleanup_push(3) that have not yet been popped, are popped  (in  the
       reverse of the order in which they were pushed) and executed.  If the thread has any thread-specific data, then,
       after the clean-up handlers have been executed, the corresponding destructor functions are called, in an unspec-
       ified order.

       When  a  thread  terminates,  process-shared resources (e.g., mutexes, condition variables, semaphores, and file
       descriptors) are not released, and functions registered using atexit(3) are not called.

       After the last thread in a process terminates, the process terminates as by calling exit(3) with an exit  status
       of zero; thus, process-shared resources are released and functions registered using atexit(3) are called.
描述:
		pthread_exit ( )函数终止调用线程,并通过retval返回一个值(如果线程为可连接)可用于调用pthread_join(3 )的同一进程中的另一线程。

pthread _ clean _ push ( 3 )建立的任何尚未弹出的清理处理程序都会弹出(在与它们被推动的顺序相反)并被执行。如果线程有任何线程特定的数据,那么,清理处理程序被执行后,相应的析构函数被调用统一的秩序。

当线程终止时,进程共享资源(例如互斥体、条件变量、信号量和文件描述符)不会被释放,使用atexit(3 )注册的函数也不会被调用。

在进程中的最后一个线程终止后,该进程通过调用具有退出状态的exit(3 )而终止;因此,过程共享资源被释放,并且使用atexit ( 3 )注册的功能被调用。
RETURN VALUE
       This function does not return to the caller.
返回值:
		这个函数没有返回值
ERRORS
       This function always succeeds.
		这个函数不会失败
  • void pthread_exit(void *retval); 参数:retval表示线程退出状态,通常传NULL
  • 这里问一个问题,使用exit将指定线程退出,可以吗?
  • 结论:线程中,禁止使用exit函数,否则会导致进程内所有线程全部退出。在不添加sleep控制输出顺序的情况下。pthread_create在循环中,几乎瞬间创建5个线程,但只有第1个线程有机会输出(或者第2个也有,也可能没有,取决于内核调度)如果第3个线程执行了exit,将整个进程退出了,所以全部线程退出了。
  • 所以,多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。
  • 另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
编写多线程程序,总结exit、return、pthread_exit各自退出效果。
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <pthread.h>
  4 #include <stdlib.h>
  5 
  6 
  7 void *tfn1(void *arg)
  8 {
  9     printf("thread 1 returning\n");
 10 
 11     return (void *)111;
 12 }
 13 
 14 void *tfn2(void *arg)
 15 {
 16     printf("thread 2 exiting\n");
 17     pthread_exit((void *)222);
 18 }
 19 
 20 void *tfn3(void *arg)
 21 {
 22     printf("thread 3 exiting\n");
 23     exit(0);
 24 }
 25 
 26 int main(void)
 27 {
 28     pthread_t tid;
 29     void *tret = NULL;
 30 
 31     pthread_create(&tid, NULL, tfn1, NULL);
 32     pthread_join(tid, &tret);
 33     printf("thread 1 exit code = %d\n\n", (int)tret);
 34 
 35     pthread_create(&tid, NULL, tfn2, NULL);
 36     pthread_join(tid, &tret);
 37     printf("thread 2 exit code = %d\n\n", (int)tret);
 38 
 39     pthread_create(&tid, NULL, tfn3, NULL);
 40     pthread_join(tid, &tret);
 41     printf("thread 3 exit code = %d\n", (int)tret);
 42 
 43     sleep(3);
 44     return 0;
 45 }

在这里插入图片描述

  • 我们可以看到在tfn1中调用return时,函数正常返回,但是注意如果return放在main主函数中的话,就会使整个进程退出;第二个函数tfn2中调用pthread_exit时,函数正常返回,这个线程也正常退出;最后一个函数tfn3调用exit时,使整个线程退出了。
  • 如果这个例子不明显的话,我们将tfn2函数和tfn3函数调换一个次序。
    在这里插入图片描述在这里插入图片描述
  • 可以看到如果在第二个函数中exit,就会使整个进程退出,从而后面的线程不会再执行。
  • 总结一下 :
    return:返回到调用者那里去。
    pthread_exit():将调用该函数的线程
    exit: 将进程退出。
获取线程退出状态
  • pthread_join 函数, 阻塞等待线程退出,获取线程退出状态。作用对应进程中 waitpid() 函数。
    int pthread_join(pthread_t thread, void **retval);
SYNOPSIS
       #include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);

DESCRIPTION
       The  pthread_join()  function waits for the thread specified by thread to terminate.  If that thread has already terminated, then pthread_join() returns immediately.  The thread specified by thread must be joinable.

       If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that the target  thread  supplied  to pthread_exit(3)) into the location pointed to by *retval.  If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.

       If multiple threads simultaneously try to join with the same thread, the results are undefined.  If  the  thread calling pthread_join() is canceled, then the target thread will remain joinable (i.e., it will not be detached).
描述:
		pthread_join ( )函数等待线程指定的线程终止。如果该线程已经终止,pthread_join ( )会立即返回。指定的线程必须是可连接的。

		如果retval不为NULL,则pthread_join ( )将目标线程的退出状态(即目标线程提供给pthread_exit(3 )的值)复制到*retval指向的位置。如果目标线程被取消,则PTHREAD _ CANCELED将被置于*retval中。

如果多个线程同时尝试与同一个线程连接,结果是未定义的。如果调用pthread_join ( )的线程被取消,那么目标线程将保持可连接性(即,它不会被分离)。
RETURN VALUE  返回值
       On success, pthread_join() returns 0; on error, it returns an error number.
       成功时,函数返回0,失败时,它将返回一个错误码
ERRORS 错误
       EDEADLK A deadlock was detected (e.g., two threads tried to join with each other); or thread specifies the  call-ing thread.

       EINVAL thread is not a joinable thread.

       EINVAL Another thread is already waiting to join with this thread.

       ESRCH  No thread with the ID thread could be found.
错误
		EDEADLK:检测到死锁(例如,两个线程试图彼此连接);或线程指定调用线程。

		EINVAL:线程不是可连接线程。

		EINVAL:另一个线程已经在等待与这个线程连接。

		ESRCH:找不到具有ID线程的线程。
  • 调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
    1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
    2. 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
    3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
    4. 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
代码:使用pthread_join函数将循环创建的多个子线程回收。
  1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<string.h>
  4 #include<stdlib.h>
  5 
  6 struct Value{
  7     int a;
  8     char ch[1024];
  9 };
 10 
 11 void* pth_func(void* arg)
 12 {
 13     int i = *((int*)arg);
 14     struct Value* val = (struct Value*)malloc(sizeof(val));
 15     val->a = i;
 16     strcpy(val->ch,"aaa");
 17     pthread_exit((void*)val);
 18 }
 19 
 20 int main()
 21 {
 22     pthread_t tid;
 23     int i,ret;
 24     struct Value* retval;
 25     for(i = 1;i <= 5; ++i)
 26     {
 27         ret = pthread_create(&tid,NULL,pth_func,(void*)&i);
 28         if(ret != 0)
 29         {
 30             fprintf(stderr,"pthread create error:%lu\n",strerror(ret));
 31             exit(0);
 32         }
 33         pthread_join(tid,(void**)&retval);
 34         printf("%dth thread :%s\n",retval->a,retval->ch);
 35         sleep(1);
 36     }
 37     sleep(2);
 38     return 0;
 39 }

在这里插入图片描述

线程分离
  • int pthread_detach(pthread_t thread);
SYNOPSIS
       #include <pthread.h>

       int pthread_detach(pthread_t thread);

DESCRIPTION
       The  pthread_detach() function marks the thread identified by thread as detached.  When a detached thread terminates, its resources are automatically released back to the system without the need for another thread  to  join with the terminated thread.

       Attempting to detach an already detached thread results in unspecified behavior.
描述
		pthread_detach ( )函数将线程标识的线程标记为已分离。当分离的线程终止时,其资源会自动释放回系统,而不需要另一个线程与终止的线程连接。

		试图分离已经分离的线程会导致未指定的行为。
RETURN VALUE  返回值
       On success, pthread_detach() returns 0; on error, it returns an error number.
		成功时,函数返回0,失败时函数返回错误编号
ERRORS
       EINVAL thread is not a joinable thread.
		EINVAL:线程不是可连接的线程
       ESRCH  No thread with the ID thread could be found.
		ESRCH:没有指定的线程ID号
  • 线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
  • 进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
线程分离的代码
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <string.h>
  5 #include <pthread.h>
  6 
  7 void *tfn(void *arg)
  8 {
  9     int n = 3;
 10 
 11     while (n--) {
 12         printf("thread count %d\n", n);
 13         sleep(1);
 14     }
 15 
 16     //return (void *)1;
 17     pthread_exit((void *)1);
 18 }
 19 
 20 int main(void)
 21 {
 22     pthread_t tid;
 23     void *tret;
 24     int err;
 25 
 26 #if 0
 27 
 28     pthread_attr_t attr;            /*通过线程属性来设置游离态*/
 29     pthread_attr_init(&attr);
 30     pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);
 31     pthread_create(&tid, &attr, tfn, NULL);
 32 
 33 #else
 34 
 35     pthread_create(&tid, NULL, tfn, NULL);
 36     pthread_detach(tid);         //让线程分离  ----自动退出,无系统残留资源
 37 
 38 #endif
 39 
 40     while (1) {
 41         err = pthread_join(tid, &tret);
 42         printf("-------------err= %d\n", err);
 43         if (err != 0)
 44             fprintf(stderr, "thread_join error: %s\n", strerror(err));
 45         else
 46             fprintf(stderr, "thread exit code %d\n", (int)tret);
 47 
 48         sleep(1);
 49     }
 50 
 51     return 0;
 52 }

在这里插入图片描述

  • 也可使用 pthread_create函数参2(线程属性)来设置线程分离。
  • 一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
取消一个线程
  • pthread_cancel函数表示杀死(取消)线程,作用对应进程中 kill() 函数。
SYNOPSIS
       #include <pthread.h>

       int pthread_cancel(pthread_t thread);

DESCRIPTION
       The  pthread_cancel()  function  sends a cancellation request to the thread thread. Whether and when the target thread reacts to the cancellation request depends on two attributes that are under the control of  that  thread: its cancelability state and type.

       A  thread’s  cancelability  state,  determined by pthread_setcancelstate(3), can be enabled (the default for new threads) or disabled.  If a thread has disabled cancellation, then a cancellation request remains  queued  until the  thread  enables cancellation.  If a thread has enabled cancellation, then its cancelability type determines when cancellation occurs.
       
       A thread’s cancellation type, determined by pthread_setcanceltype(3), may be  either  asynchronous  or  deferred (the  default  for  new  threads).  Asynchronous cancelability means that the thread can be canceled at any time (usually immediately, but the system does not guarantee this).  Deferred cancelability means  that  cancellation  will  be  delayed until the thread next calls a function that is a cancellation point.  A list of functions that are or may be cancellation points is provided in pthreads(7).

       When a cancellation requested is acted on, the following steps occur for thread (in this order):

       1. Cancellation clean-up handlers are popped (in the reverse of the order in which they were pushed) and called.   (See pthread_cleanup_push(3).)  

       2. Thread-specific data destructors are called, in an unspecified order.  (See pthread_key_create(3).)

       3. The thread is terminated.  (See pthread_exit(3).)

       The  above  steps  happen  asynchronously  with  respect  to  the  pthread_cancel()  call;  the return status of pthread_cancel() merely informs the caller whether the cancellation request was successfully queued.

       After a canceled thread has terminated, a join with that thread using pthread_join(3)  obtains  PTHREAD_CANCELED as the thread’s exit status.  (Joining with a thread is the only way to know that cancellation has completed.)

描述
		pthread_cancel ( )函数向线程发送取消请求。目标线程是否以及何时对取消请求做出反应取决于该线程控制的两个属性:其可取消状态和类型。

		由pthread_setcancelstate(3 )确定的线程的可取消状态可以启用(新线程的默认值)或禁用。如果线程已禁用取消,则取消请求将一直排队,直到该线程启用取消。如果线程已启用取消,则它的可取消性类型将确定取消发生的时间。

		由pthread_setcanceltype(3 )确定的线程取消类型可以是异步的,也可以是延迟的(新线程的默认值)。异步可取消性意味着线程可以随时取消(通常是立即取消,但系统不保证这一点)。延迟可取消性意味着取消将被延迟,直到线程下一次调用一个作为取消点的函数。pthreads(7 )中提供了作为或可能作为取消点的功能列表。

		执行取消请求时,线程将执行以下步骤(按此顺序) :

		1 .取消清理处理程序被弹出(与它们被推送的顺序相反)并被调用。(参见pthread _ clean _ push ( 3 )。) )

		2 .线程特定的数据析构函数以未指定的顺序被调用。(参见pthread _ key _ create ( 3 )。) )

		3 .线程被终止。(见pthread _ exit ( 3 )。) )

		上述步骤相对于pthread_cancel ( )调用异步发生;pthread_cancel ( )的返回状态只是通知呼叫者取消请求是否已成功排队。

		取消的线程终止后,使用pthread_join(3 )与该线程的联接将获得PTHREAD _ CANCELED作为线程的退出状态。(加入线程是知道取消已经完成的唯一方法。) )
	
RETURN VALUE  返回值
       On success, pthread_cancel() returns 0; on error, it returns a non-zero error number.
		成功时,函数返回0,失败时,返回非0的错误编号
ERRORS
       ESRCH  No thread with the ID thread could be found.
		ESRCH:没有此ID的线程
  • 注意:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)
  • 取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。
  • 可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthreestcancel函数自行设置一个取消点。
  • 被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。因此当我们对一个已经被取消的线程使用pthread_join回收时,得到的返回值为-1。
代码:终止线程的三种方法。注意“取消点”的概念。
  1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 
  7 void* sfn(void* arg)
  8 {
  9 #if 0
 10     printf("i am going to die : %lu\n",pthread_self());
 11     sleep(1);
 12 #else
 13     pthread_testcancel();
 14 #endif
 15     
 16     return ((void*)111);
 17 }
 18 
 19 int main()
 20 {   
 21     pthread_t tid;
 22     int ret;
 23     int* retval;
 24     
 25     ret = pthread_create(&tid,NULL,sfn,NULL);
 26     if(ret != 0)
 27     {
 28         fprintf(stderr,"pthread create error : %lu\n",strerror(ret));
 29         exit(0);
 30     }
 31 
 32     ret = pthread_cancel(tid);
 33     if(ret != 0)
 34     {
 35         fprintf(stderr,"pthread cancel error : %lu\n",strerror(ret));
 36         exit(1);
 37     }
 38 
 39     ret = pthread_join(tid,(void**)&retval);
 40     if(ret != 0)
 41     {
 42         fprintf(stderr,"pthread_join error : %lu\n",strerror(ret));
 43         exit(2);
 44     }
 45     printf("return val :%d \n",(int)retval);
 46 
 47     return 0;
 48 }

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

  • 我们可以看到获取一个已经取消的线程,pthread_join函数会返回 -1。而且pthread_cancel 函数必须要有一个取消点,如果sfn函数中没有调用系统函数并且没有调用pthread_testcancel函数,那么这个线程将不会被取消。
终止线程方式

总结:终止某个线程而不终止整个进程,有三种方法:

  1. 从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。
  2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
  3. 线程可以调用pthread_exit终止自己。
线程使用注意事项
  1. 主线程退出其他线程不退出,主线程应调用pthread_exit
  2. 避免僵尸线程
    pthread_join
    pthread_detach
    pthread_create指定分离属性
    被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
  3. malloc和mmap申请的内存可以被其他线程释放
  4. 应避免在多线程模型中调用fork除非,马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
  5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值