这篇文章主要介绍了简单了解C语言中主线程退出对子线程的影响,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

对于程序来说,如果主进程在子进程还未结束时就已经退出,那么Linux内核会将子进程的父进程ID改为1(也就是init进程),当子进程结束后会由init进程来回收该子进程。

那如果是把进程换成线程的话,会怎么样呢?假设主线程在子线程结束前就已经退出,子线程会发生什么?

在一些论坛上看到许多人说子线程也会跟着退出,其实这是错误的,原因在于他们混淆了线程退出和进程退出概念。实际的答案是主线程退出后子线程的状态依赖于它所在的进程,如果进程没有退出的话子线程依然正常运转。如果进程退出了,那么它所有的线程都会退出,所以子线程也就退出了。

主线程先退出

先来看一个主线程先退出的例子:

#include

#include

#include

void* func(void* arg)

{

pthread_t main_tid = *static_cast(arg);

pthread_cancel(main_tid);

while (true)

{

//printf("child loops

");

}

return NULL;

}

int main(int argc, char* argv[])

{

pthread_t main_tid = pthread_self();

pthread_t tid = 0;

pthread_create(&tid, NULL, func, &main_tid);

while (true)

{

printf("main loops

");

}

sleep(1);

printf("main exit

");

return 0;

}

把主线程的线程号传给子线程,在子线程中通过pthread_cancel终止主线程使其退出。运行程序,可以发现在打印了一定数量的「main loops」之后程序就挂起了,但却没有退出。

主线程因为被子线程终止了,所有没有看到「main exit」的打印。子线程终止了主线程后进入了死循环while中,所以程序看起来像挂起了。如果我们让子进程while循环中的打印语句生效再运行就可以发现程序会一直打印「child loops」字样。

主线程被子线程终止了,但他们所依赖的进程并没有退出,所以子线程依然正常运转。

主线程随进程一起退出

之前看到一些人说如果主线程先退出了,子线程也会跟着退出,其实他们混淆了线程退出和进程退出的概念。下面这个例子代表了他们的观点:

void* func(void* arg)

{

while (true)

{

printf("child loops

");

}

return NULL;

}

int main(int argc, char* argv[])

{

pthread_t main_tid = pthread_self();

pthread_t tid = 0;

pthread_create(&tid, NULL, func, &main_tid);

sleep(1);

printf("main exit

");

return 0;

}

运行上面的代码,会发现程序在打印一定数量的「child loops」和一句「main exit」之后退出,并且在退出之前的最后一句打印是「main exit」。

按照他们的逻辑,你看,因为主线程在打印完「main exit」后退出了,然后子线程也跟着退出了,所以随后就没有子线程的打印了。

但其实这里是混淆了进程退出和线程退出的概念了。实际的情况是主线程中的main函数执行完ruturn后弹栈,然后调用glibc库函数exit,exit进行相关清理工作后调用_exit系统调用退出该进程。所以,这种情况实际上是因为进程运行完毕退出导致所有的线程也都跟着退出了,并非是因为主线程的退出导致子线程也退出。

Linux线程模型

实际上,posix线程和一般的进程不同,在概念上没有主线程和子线程之分(虽然在实际实现上还是有一些区分),如果仔细观察apue或者unp等书会发现基本看不到「主线程」或者「子线程」等词语,在csapp中甚至都是用「对等线程」一词来描述线程间的关系。

在Linux 2.6以后的posix线程都是由用户态的pthread库来实现的。在使用pthread库以后,在用户视角看来,每一个tast_struct就对应一个线程(tast_struct原本是内核对应一个进程的结构),而一组线程以及他们所共同引用的一组资源就是进程。从Linux 2.6开始,内核有了线程组的概念,tast_struct结构中增加了一个tgid(thread group id)字段。getpid(获取进程号)通过系统调用返回的也是tast_struct中的tgid,所以tgid其实就是进程号。而tast_struct中的线程号pid字段则由系统调用syscall(SYS_gettid)来获取。

当线程收到一个kill致命信号时,内核会将处理动作施加到整个线程组上。为了应付「发送给进程的信号」和「发送给线程的信号」,tast_struct里面维护了两套signal_pending,一套是线程组共用的,一套是线程独有的。通过kill发送的致命信号被放在线程组共享的signal_pending中,可以任意由一个线程来处理。而通过pthread_kill发送的信号被放在线程独有的signal_pending中,只能由本线程来处理。

关于线程与信号,apue有这么几句:

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变。这样如果一个线程选择忽略某个信号,而其他的线程可以恢复信号的默认处理行为,或者是为信号设置一个新的处理程序,从而可以撤销上述线程的信号选择。

如果信号的默认处理动作是终止该进程,那么把信号传递给某个线程仍然会杀掉整个进程。

例如一个程序a.out创建了一个子线程,假设主线程的线程号为9601,子线程的线程号为9602(它们的tgid都是9601),因为默认没有设置信号处理程序,所以如果运行命令kill 9602的话,是可以把9601和9602这个两个线程一起杀死的。如果不知道Linux线程背后的故事,可能就会觉得遇到灵异事件了。

另外系统调用syscall(SYS_gettid)获取的线程号与pthread_self获取的线程号是不同的,pthread_self获取的线程号仅仅在线程所依赖的进程内部唯一,在pthread_self的man page中有这样一段话:

Thread IDs are guaranteed to be unique only within a process. A thread ID may be reused after a terminated thread has been joined, or a detached thread has terminated.

所以在内核中唯一标识线程ID的线程号只能通过系统调用syscall(SYS_gettid)获取。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持云海天教程。

c语言主线程退出子线程,简单了解C语言中主线程退出对子线程的影响相关推荐

  1. swoole mysql 协程_swoole-orm: 基于swoole的mysql协程连接池,简单封装。实现多个协程间共用同一个协程客户端。参考thinkphp-orm...

    swoole-orm 基于swoole的mysql协程连接池,简单封装. 实现多个协程间共用同一个协程客户端 感谢完善 [1]:nowbe -> 新增数据返回insert_id 版本 v0.0. ...

  2. 【C语言】一文带你简单了解C语言

    这里写目录标题) 引言 C语言概述 基础语法 数据类型 运算符 循环语句 分支语句 函数 数组 指针 文件操作 内存管理 高级特性 结构体 枚举类型 联合体 预处理器 应用场景 操作系统 编译器 游戏 ...

  3. 一天学会shell语言,shell教程,shell简单入门,shell中文文档

        shell语言是一门linux系统下的工具语言,主要用于写一些linux系统下的操作命令,实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核.或者直接理解为shell命 ...

  4. c语言的各种类型的指针,简单总结C语言中各种类型的指针的概念

    2016 这篇文章主要简单总结了C语言中各种类型的指针的概念,指针可以说是C语言本身所具有的最大特性,平时根据不同使用场合习惯地将其简单分类,需要的朋友可以参考下 C语言中有很多关于指针的使用,指针也 ...

  5. c语言整形如何正确使用除法,简单的C语言移位计算整形乘法和除法值

    最近一直都没写什么文章,随便写点最近看到的东西.首先要提一点,在SOC系统中,运算速度一般是移位>乘法>除法. 1.乘法. 最简单的A*B,用C语言for循环 for(int i=0;i ...

  6. java语言编写简易表达式_将简单的表达语言放入Java

    小编典典 您可以看到如何传递所有参数: ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = m ...

  7. 坚果手机c语言编程软件,PAT-B1018-锤子剪刀布(C语言实现)

    题目要求 大家应该都会玩"锤子剪刀布"的游戏:两人同时给出手势,胜负规则如图所示: 剪刀石头布 现给出两人的交锋记录,请统计双方的胜.平.负次数,并且给出双方分别出什么手势的胜算最 ...

  8. java 线程数_在虚拟机中是什么限制java线程数量?这方面涉及哪些调优?

    首先要说明一点,Java线程的实现是基于底层系统的线程机制来实现的,程序中开的线程并不全部取决于JVM虚拟机栈,而是取决于CPU,操作系统,其他进程,Java的版本.JVM的线程与计算机本身性能相关. ...

  9. 线程A向队列Q中不停写入数据,线程B从列队Q中不停读取数据(只要Q中有数据)。

    //接口中有两个 一个是向队列中写push方法  一个是从队列中读 public interface StackInterface {public void push(int n);public in ...

最新文章

  1. 2018秋寒假作业4—PTA编程总结1
  2. xcode多target
  3. typedef与#define宏区别
  4. 红米android 版本,#MIUI#关于红米手机4高配版 Android版本适配的说明【miui9吧】_百度贴吧...
  5. Web系统开发构架再思考-前后端的完全分离
  6. Velocity.js中文文档
  7. 576. 出界的路径数
  8. Enterprise Library Logging App Block的时区问题
  9. 简化版XP按装IIS5.1实录
  10. 电子邮件地址中服务器怎么看,你如何检查电子邮件服务器(gmail)中的某个地址,并基于该地址运行一些东西?...
  11. java 学习视频 从基础到精通以及框架
  12. 淘宝用户行为分析(Python)
  13. 失恋33天——我用57天考了一个5A
  14. word自动编号与文字间距太大怎么办
  15. 用计算机完成下表的视距测量计算公式,中南大学工程测量计算题库及参考答案...
  16. Linux SCSI设备容量打印代码分析
  17. 一文教你学会使用GitHub!(附视频)
  18. 安装qtp时,出现I/O设备错误的报错,该怎么办呢?
  19. 1.初识C语言----什么是C语言,为什么要学C语言?
  20. UML入门1:事物和事物关系简介

热门文章

  1. 运用KDJ交叉形态把握短期买卖点
  2. MATLAB-数据类型之复数、字符串
  3. 安全帽检测系统工地测试分析
  4. qq自由幻想系统不能提供服务器,《QQ自由幻想》角色转服常见问题集锦(上)...
  5. kali攻击wifi、破解wifi密码详细教程(二)
  6. LeetCode5454-子矩阵计算
  7. 20140228老沙的感觉
  8. 【STM32】入门(零):keil安装、科学使用、芯片包安装
  9. python使用pandas拆分excel表并导出(2)
  10. 用python做一个银行取款系统_我的第一个Python小程序,大老虎银行存取款机程序!支持转账...