一 Pthread 并发编程——深入剖析线程基本元素和状态( 四 )

在上面的程序当中我们使用 mmap 系统调用在共享库空间申请了一段内存空间,并且将其做为栈空间,我们在这里就不将程序执行的结果放出来了,上面整个程序和前面的程序相差不大,只是在申请内存方面发生了变化,总体的方向是不变的 。
根据前面知识的学习,我们可以知道多个线程可以共享同一个进程虚拟地址空间,我们只需要给每个线程申请一个栈空间让线程执行起来就行,基于此我们可以知道多个线程的执行流和大致的内存布局如下图所示:

一 Pthread 并发编程——深入剖析线程基本元素和状态

文章插图
在上图当中不同的线程拥有不同的栈空间和每个线程自己的寄存器现场,正如上图所示,栈空间可以是在堆区也可以是在共享库的映射区域,只需要给线程提供栈空间即可 。
深入理解线程的状态在 pthread 当中给我们提供了一个函数 pthread_cancel 可以取消一个正在执行的线程,取消正在执行的线程之后会将线程的退出状态(返回值)设置成宏定义 PTHREAD_CANCELED。我们使用下面的例子去理解一下线程取消的过程:
#include <stdio.h>#include <pthread.h>#include <assert.h>void* task(void* arg) { while(1) {pthread_testcancel(); // 测试是否被取消执行了}return NULL;}int main() {void* res;pthread_t t;pthread_create(&t, NULL, task, NULL);int s = pthread_cancel(t); // 取消函数的执行if(s != 0)fprintf(stderr, "cancel failed\n");pthread_join(t, &res);assert(res == PTHREAD_CANCELED);return 0;}在上面的程序当中我们在主线程当中使用函数 pthread_cancel 函数取消线程的执行,编译执行上面的程序是可以通过的,也就是说程序正确执行了,而且 assert 也通过了 。我们先不仔细去分析上面的代码的执行流和函数的意义 。我们先需要了解一个线程的基本特性 。
与线程取消执行相关的一共有两个属性,分别是:
  • 取消执行的状态,线程的取消执行的状态一共有两个:
    • PTHREAD_CANCEL_ENABLE:这个状态表示这个线程是可以取消的,也是线程创建时候的默认状态 。
    • PTHREAD_CANCEL_DISABLE:这个状态表示线程是不能够取消的,如果有一个线程发送了一个取消请求,那么这个发送取消消息的线程将会被阻塞直到线程的取消状态变成 PTHREAD_CANCEL_ENABLE。
  • 取消执行的类型,取消线程执行的类型也有两种:
    • PTHREAD_CANCEL_DEFERRED:当一个线程的取消状态是这个的时候,线程的取消就会被延迟执行,知道线程调用一个是取消点的(cancellation point)函数,比如 sleep 和 pthread_testcancel ,所有的线程的默认取消执行的类型就是这个类型 。
    • PTHREAD_CANCEL_ASYNCHRONOUS:如果线程使用的是这个取消类型那么线程可以在任何时候被取消执行,当他接收到了一个取消信号的时候,马上就会被取消执行,事实上这个信号的实现是使用 tgkill 这个系统调用实现的 。
事实上我们很少回去使用 PTHREAD_CANCEL_ASYNCHRONOUS ,因为这样杀死一个线程会导致线程还有很多资源没有释放,会给系统带来很大的灾难,比如线程使用 malloc 申请的内存空间没有释放,申请的锁和信号量没有释放,尤其是锁和信号量没有释放,很容易造成死锁的现象 。
有了以上的知识基础我们现在可以来谈一谈前面的两个函数了:
  • pthread_cancel(t) :是给线程 t 发送一个取消请求 。
  • pthread_testcancel():这个函数是一个取消点,当执行这个函数的时候,程序就会取消执行 。
现在我们使用默认的线程状态和类型创建一个线程执行死循环,看看线程是否能够被取消掉:

经验总结扩展阅读