加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 钦州站长网 (https://www.0777zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

第5篇 Linux系统编程-优雅地取消线程

发布时间:2022-10-19 12:54:46 所属栏目:Unix 来源:
导读:  上文遗留了一些问题,究竟如何优雅地取消一个线程?其实取消一个线程我们会用到如下Unix/Linux的系统调用

  int pthread_cancel(pthread_t tid);
  顾名思义就是传入需要取消线程的id,成功就返回0,但
  上文遗留了一些问题,究竟如何优雅地取消一个线程?其实取消一个线程我们会用到如下Unix/Linux的系统调用
 
  int pthread_cancel(pthread_t tid);
  顾名思义就是传入需要取消线程的id,成功就返回0,但只是发送一个请求而已,即便发送成功向子线程成功发送请求,也不意味着id所标识的线程能立即终止。
 
  那么一个线程什么时候取消?如何取消?这又牵涉线程的另外几个概念就是线程的取消状态.
 
  取消状态
 
  就是指线程对接收到取消信号的"态度",要么“忽略”要么“响应”,Linux的线程API,提供了一个系统调用来设定一个子线程的取消方式。该系统调用设置取消信号的动作
 
  int pthread_setcancelstate(int state,int* oldstate)
  参数state可选的值
 
  参数oldstate:如果不为NULL则存入原来的取消状态以便之后恢复。
 
  OK,明确取消状态后,那么现在又产生另一个疑问了:如果是默认的取消状态,那么是立即取消呢?还是待会取消呢?这需要引出另外一个概念"取消类型".
 
  取消类型
 
  这是指线程对接收的取消信号的实际响应动作,它确定了线程取消的时机,同理线程库的API有对应的系统调用
 
  int pthread_setcanceltype(int type,int *oldtype)
  参数type有两个可选的值,它们仅当取消状态设定为PTHREAD_CANCEL_ENABLE时才有效
 
  参数oldtype,如果不为NULL则存入原来的取消l类型以便之后恢复。
 
  取消执行点
 
  就是线程在接收到取消信号后,其线程执行的回调函数上下文必须有一个可识别的执行点,让线程能够将其识别为采取终止线程的目标位置。而取消执行点实质上是一个函数,通常是具有阻塞的系统调用,例如的绝大部分i/o函数等,可以通过man pthreads命令查看pthreads手册Cancellation point的文档部分,罗列了一大堆被定义为取消执行点系统调用函数
 
  我们通过一个灰常无聊的示例来展示了主线程调用phtread_cancel函数如何和子线程交互和命令子线程终止。
 
  void* subth(void* args){
      pthread_t id=pthread_self();
      int sta=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
      if(sta!=0){
          printf("线程:%lu遇到一些问题无法执行\n",id);
      }
      printf("线程:%lu正在执行\n",id);
      printf("线程:%lu暂停执行5秒\n",id);
      sleep(5);
      printf("线程:%lu收到一个取消信号\n",id);
      sta=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
      
      if(sta!=0){
          printf("线程:%lu取消失败\n",id);
      }
      printf("线程:%lu正被主线程取消\n",id);
      printf("线程:%lu还在运行吗\n",id);
  }
  int main(int argc,char* argv){
      pthread_t tid;
      int err,c,k;
      void* ret;
      printf("主线程:%lu创建子线程\n",pthread_self());
      err=pthread_create(&tid,NULL,subth,NULL);
      if(err!=0){
          printf("创建线程%lu失败\n",tid);
          return 0;
      }
      sleep(2);
      c=pthread_cancel(tid);
      if(c!=0){
          printf("取消线程%lu失败\n",tid);
      }
      k=pthread_join(tid,&ret);
      printf("线程%lu的返回代码%d\n",tid,(int*)ret);
  }
  编译后的程序输出如下,这段代码虽然简单,往下翻之前,建议你可以思考并动笔划出一些引导线,验证一下你对线程间交互的理解和笔者有何偏差。
 
  首先,当主线程main函数创建子线程并执行subth回调函数,子线程的subth函数即时执行,并且子线程设调用pthread_setcancelstate将自己本身的取消状态设定为禁用状态。意味着eip指针在未到达代码45行之前,不轮其他线程调用pthread_cancel向其发送取消信号,subth所在的线程都无视取消信号的请求。另外需要说的是,此时subth所在线程在执行到41行之前都处于运行状态.
 
  与此同时,main主线程,其函数在执行到第69行的sleep之前,即54行到68行此时的主线程都处于运行状态。也就是说main线程与subth子线程之间的关系是属于线程异步.此时既然属于异步的线程,main主线程和subth子线程在各自未执行至sleep之前都是非阻塞的。
 
  在本示例中main函数从创建subth线程后到其sleep调用之前的代码量略少于subth线程的代码量,因此main主线程总先于subth子线程调用sleep并进入阻塞状态。随后subth子线程也随即进入阻塞状态,但这种状态几乎只维持2秒之内。见如下图
 
  main线程和subth子线程彼此处于阻塞状态,单仅持续2秒钟而已。
 
  上图有个细节,我们知道第43行的printf函数是一个线程取消执行点,那么当subth子线程5秒后从堵塞状态切换为运行状态后,执行到43行的printf函数,你认为subth子线程会终止线程吗?
 
  回答:由于subth子线程在起始将取消状态设定为禁用,因此执行到第43行的取消执行点(printf函数)仍然会忽略取消请求。继续执行后面的代码。
 
  重要概念:刚才说任何一个取消请求,并不会直接发送到目标子线程,而是先由系统内核捕获并将该取消请求加入一个缓存队列中,直到子线程启用取消状态。当线程变为可取消,并且子线程每执行到取消点时,会从检测系统内核的缓存队列是否有对应自己的取消请求,若有就在当前取消点终止线程,并且释放自己所占用的资源(栈、堆、CPU时钟、寄存器等)
 
  直到subth子线程执行到45行,子线程将启用取消状态,随即到第50行取消执行点printf函数,subth子线程会查询内核的缓冲队列有自己取消请求,因此就在第50行开始终止函数,因此子线程subth函数的第51行第二个取消执行点不会被执行的。一切如下图所示

  当subth子线程从50行终止后unix线程切换,内核的rip指针重新指向主线程的第78行,此时主线程从堵塞状态切换到运行状态,并执行自己如下的C代码。如下图所示。
 
  小结
 
  Ok,上面的示例,我们详细地介绍了主线程和子线程如何交互的,以及解决了前篇遗留的一些问题--线程间交互期间的线程状态变化,以及取消一个线程的重要通讯机制这些都是由内核在幕后维持线程间的通讯机制的--缓存队列。关于取消执行点的底层原理,也参看该文章。
 

(编辑:PHP编程网 - 钦州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章