返回顶部
首页 > 资讯 > 操作系统 >linux线程该如何浅析
  • 228
分享到

linux线程该如何浅析

2023-06-16 17:06:35 228人浏览 薄情痞子
摘要

本篇文章为大家展示了linux线程该如何浅析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各

本篇文章为大家展示了linux线程该如何浅析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各种资源. 而线程则是真正的执行实体. 为了让进程完成一定的工作, 进程必须至少包含一个线程.

进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等;线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等;

然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源(注意这里说的是linux中的进程).通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源. 这样的子进程通常称为轻量级进程.

linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.

但是, 一组线程并不仅仅是引用同一组资源就够了, 它们还必须被视为一个整体.对此, POSIX标准提出了如下要求:

  • (1) 查看进程列表的时候, 相关的一组task_struct应当被展现为列表中的一个节点;

  • (2) 发送给这个"进程"的信号(对应kill系统调用), 将被对应的这一组task_struct所共享, 并且被其中的任意一个"线程"处理;

  • (3) 发送给某个"线程"的信号(对应pthread_kill), 将只被对应的一个task_struct接收, 并且由它自己来处理;

  • (4) 当"进程"被停止或继续时(对应SIGSTOP/SIGCONT信号), 对应的这一组task_struct状态将改变;

  • (5) 当"进程"收到一个致命信号(比如由于段错误收到SIGSEGV信号), 对应的这一组task_struct将全部退出;

  • (6) 等等(以上可能不够全);

1. Linuxthreads

在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib. linuxthreads利用前面提到的轻量级进程来实现线程, 但是对于POSIX提出的那些要求, linuxthreads除了第5点以外, 都没有实现(实际上是无能为力):

  • (1) 如果运行了A程序, A程序创建了10个线程, 那么在shell下执行ps命令时将看到11个A进程, 而不是1个(注意, 也不是10个, 下面会解释);

  • (2) 不管是kill还是pthread_kill, 信号只能被一个对应的线程所接收;

  • (3) SIGSTOP/SIGCONT信号只对一个线程起作用。

还好linuxthreads实现了第5点, 我认为这一点是最重要的. 如果某个线程"挂"了, 整个进程还在若无其事地运行着, 可能会出现很多的不一致状态. 进程将不是一个整体, 而线程也不能称为线程. 或许这也是为什么linuxthreads虽然与POSIX的要求差距甚远, 却能够存在, 并且还被使用了好几年的原因吧~。但是, linuxthreads为了实现这个"第5点", 还是付出了很多代价, 并且创造了linuxthreads本身的一大性能瓶颈.

接下来要说说, 为什么A程序创建了10个线程, 但是ps时却会出现11个A进程了. 因为linuxthreads自动创建了一个管理线程. 上面提到的"第5点"就是靠管理线程来实现的.当程序开始运行时, 并没有管理线程存在(因为尽管程序已经链接了pthread库, 但是未必会使用多线程). 程序***次调用pthread_create时, linuxthreads发现管理线程不存在, 于是创建这个管理线程. 这个管理线程是进程中的***个线程(主线程)的儿子.然后在pthread_create中, 会通过pipe向管理线程发送一个命令, 告诉它创建线程. 即是说, 除主线程外, 所有的线程都是由管理线程来创建的, 管理线程是它们的父亲.于是, 当任何一个子线程退出时, 管理线程将收到SIGUSER1信号(这是在通过clone创建子线程时指定的). 管理线程在对应的sig_handler中会判断子线程是否正常退出, 如果不是, 则杀死所有线程, 然后自杀.那么, 主线程怎么办呢? 主线程是管理线程的父亲, 其退出时并不会给管理线程发信号. 于是, 在管理线程的主循环中通过getppid检查父进程的ID号, 如果ID号是1, 说明父亲已经退出, 并把自己托管给了init进程(1号进程). 这时候, 管理线程也会杀掉所有子线程, 然后自杀. 那么, 如果主线程是调用pthread_exit主动退出的呢? 按照posix的标准,这种情况下其他子线程是应该继续运行的. 于是, 在linuxthreads中, 主线程调用pthread_exit以后并不会真正退出, 而是会在pthread_exit函数中阻塞等待所有子线程都退出了, pthread_exit才会让主线程退出. (在这个等等过程中, 主线程一直处于睡眠状态.)

可见, 线程的创建与销毁都是通过管理线程来完成的, 于是管理线程就成了linuxthreads的一个性能瓶颈. 创建与销毁需要一次进程间通信, 一次上下文切换之后才能被管理线程执行, 并且多个请求会被管理线程串行地执行.

2. NPTL

到了linux 2.6, glibc中有了一种新的pthread线程库--NPTL(Native POSIX Threading Library). NPTL实现了前面提到的POSIX的全部5点要求. 但是, 实际上, 与其说是NPTL实现了, 不如说是linux内核实现了.

在linux 2.6中, 内核有了线程组的概念, task_struct结构中增加了一个tgid(thread group id)字段. 如果这个task是一个"主线程", 则它的tgid等于pid, 否则tgid等于进程的pid(即主线程的pid).在clone系统调用中, 传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid).

类似的XXid在task_struct中还有两个:task->signal->pgid保存进程组的打头进程的pid、task->signal->session保存会话打头进程的pid。通过这两个id来关联进程组和会话。

有了tgid, 内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程, 也就知道在什么时候该展现它们, 什么时候不该展现(比如在ps的时候, 线程就不要展现了).

而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid, 而tast_struct中的pid则由gettid系统调用来返回.

在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程是10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。

我们可以作如下验证,有程序段test.cpp如下:

#include< unistd.h> #include< stdlib.h> #include< stdio.h> #include< pthread.h> using namespace std;  void* doit(void*);  int main(void)  {  pthread_t tid;  pthread_create(&tid,NULL,doit,NULL);  pause();//主线程挂起(否则主线程终止,子线程也就挂了)  }  void* doit(void* agr)  {  printf("thread is created!\n");  pause(); //挂起线程  }

这个程序创建一个线程后挂起,子线程在输出“thread is created!”也挂起。运行结果如图1.

linux线程该如何浅析

图1

之后查看进程pid,发现a.out的pid是23130,我们使用kill终止pid为23131的进程(注意ps中并没有这个进程),如图2.

linux线程该如何浅析

图2

但是结果发现,a.out进程也终止了,如图3.其原因就是23131就是所创建线程的pid,线程异常终止了,进程也就终止了。

linux线程该如何浅析

图3

为了应付"发送给进程的信号"和"发送给线程的信号",task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的.通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.当线程停止/继续, 或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中.

3. NGPT

说到这里, 也顺便提一下NGPT(Next Generation POSIX Threads). 上面提到的两种线程库使用的都是内核级线程(每个线程都对应内核中的一个调度实体), 这种模型称为1:1模型(1个线程对应1个内核级线程);而NGPT则打算实现M:N模型(M个线程对应N个内核级线程), 也就是说若干个线程可能是在同一个执行实体上实现的.

线程库需要在一个内核提供的执行实体上抽象出若干个执行实体, 并实现它们之间的调度. 这样被抽象出来的执行实体称为用户级线程.大体上, 这可以通过为每个用户级线程分配一个栈, 然后通过longjmp的方式进行上下文切换. (百度一下"setjmp/longjmp", 你就知道.)但是实际上要处理的细节问题非常之多. 目前的NGPT好像并没有实现所有预期的功能, 并且暂时也不准备去实现.

用户级线程的切换显然要比内核级线程的切换快一些, 前者可能只是一个简单的长跳转, 而后者则需要保存/装载寄存器, 进入然后退出内核态. (进程切换则还需要切换地址空间等.)而用户级线程则不能享受多处理器, 因为多个用户级线程对应到一个内核级线程上, 一个内核级线程在同一时刻只能运行在一个处理器上.

不过, M:N的线程模型毕竟提供了这样一种手段, 可以让不需要并行执行的线程运行在一个内核级线程对应的若干个用户级线程上, 可以节省它们的切换开销.据说一些类UNIX系统(如Solaris)已经实现了比较成熟的M:N线程模型, 其性能比起linux的线程还是有着一定的优势.

上述内容就是linux线程该如何浅析,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网操作系统频道。

--结束END--

本文标题: linux线程该如何浅析

本文链接: https://lsjlt.com/news/284575.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • linux线程该如何浅析
    本篇文章为大家展示了linux线程该如何浅析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。在许多经典的操作系统教科书中, 总是把进程定义为程序的执行实例, 它并不执行什么, 只是维护应用程序所需的各...
    99+
    2023-06-16
  • 如何浅析Java多线程程序的设计机制
    本篇文章为大家展示了如何浅析Java多线程程序的设计机制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。浅析Java多线程程序设计机制多线程是Java语言的一大特性,多线程就是同时存在N个执行体,按几...
    99+
    2023-06-03
  • 浅析Python中线程以及线程阻塞
    目录进程和线程的概念进程概念线程概念线程优缺点在python中如何使用线程线程阻塞也很重要总结本文所依赖的环境为: 进程和线程的概念 进程概念 我们想运行一个程序,首先会将该程序从...
    99+
    2023-05-17
    Python线程 Python线程阻塞
  • Java多线程之ThreadLocal浅析
    目录介绍什么是ThreadLocal?ThreadLocal 的作用是什么?如何使用ThreadLocal如何创建一个ThreadLocal实例ThreadLocal的实现原理Thr...
    99+
    2023-05-17
    Java多线程 Java ThreadLocal 多线程ThreadLocal
  • 如何解析Linux系统多线程编程
    这篇文章的内容主要围绕如何解析Linux系统多线程编程进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!Linux下的多线程编程1 引言  线程(thr...
    99+
    2023-06-28
  • 浅析android中的线程封装
    简单写一下android 对线程 的c++封装~~~其实API已经写得很清楚了~~ 封装 的文件 :/frameworks/base/include/utils/threads...
    99+
    2022-06-06
    封装 线程 Android
  • Python线程池的实现浅析
    目录一、序言二、正文1、Future 对象2、提交函数自动创建 Future 对象3、future.set_result 到底干了什么事情4、提交多个函数5、使用 map 来提交多个...
    99+
    2024-04-02
  • 浅谈java线程状态与线程安全解析
    目录1.线程的几种状态1.1 线程的状态1.2 线程状态的转移 2.有关线程安全问题2.1 一个简单的例子2.2 造成线程不安全的原因1.线程的几种状态 1.1 线程的状态...
    99+
    2023-02-03
    java线程状态 java线程安全
  • 如何分析Linux 下线程池的使用
    这期内容当中小编将会给大家带来有关如何分析Linux 下线程池的使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。处理多线程的方式可以采用线程池,可以将“生产者”线程提出任务列表添加到“任务列表”,然后一...
    99+
    2023-06-28
  • 如何分析线程、
    本篇文章为大家展示了如何分析线程、,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 1、开启线程的三种方式1)继承Thread类,重写run()方法,在run()方法体中编写要完成的任务 n...
    99+
    2023-06-16
  • 深入浅析Node.js单线程模型
    Node.js采用 事件驱动 和 异步I/O 的方式,实现了一个单线程、高并发的运行时环境,而单线程就意味着同一时间只能做一件事,那么Node.js如何利用单线程来实现高并发和异步I/O?本文将围绕这个问题...
    99+
    2022-06-04
    单线程 模型 Node
  • 深入浅析Node中的进程和线程
    // app.js const Koa = require('koa') const router = require('koa-router')() const app = new Koa() // 用来...
    99+
    2023-05-14
    nodejs​ 进程 线程
  • Python线程编程中的Thread该如何理解
    Python线程编程中的Thread该如何理解,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、线程编程(Thread)1、线程基本概念1.1、什么事线程线程被称为轻量级的...
    99+
    2023-06-22
  • 如何分析Linux多线程可重入函数
    如何分析Linux多线程可重入函数,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Reentrant和Thread-safe在单线程程序中,整个程序都是顺序执行的,一个函数在同一...
    99+
    2023-06-28
  • 如何浅析Fedora 7的Linux嵌入式
    这篇文章将为大家详细讲解有关如何浅析Fedora 7的Linux嵌入式,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。下面就这个问题来详细说说Linux嵌入式。这里,需要利用内核nfs功能,将...
    99+
    2023-06-17
  • Python多线程与同步机制浅析
    目录线程实现Thread类函数方式继承方式同步机制同步锁Lock条件变量Condition信号量Semaphore事件Event屏障BarrierGIL全局解释器锁线程实现 Pyth...
    99+
    2022-12-22
    Python多线程 Python同步机制
  • 怎么实现JAVA 多线程的浅析
    这期内容当中小编将会给大家带来有关怎么实现JAVA 多线程的浅析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。JAVA 的多线程浅析一、JAVA 语言的来源、及特点在这个高速信息的时代,商家们纷纷把信息、...
    99+
    2023-06-03
  • JavaEE该如何分析
    JavaEE该如何分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。JavaEE 是 J2EE的一个新的名称,之所以改名,目的还是让大家清楚J2EE只是Java企业应用。在...
    99+
    2023-06-03
  • 浅谈springboot如何保证多线程安全
    目录如何保证多线程安全1.springboot在多线程并发访问下是怎么做的2.controller在多线程下如何尽可能保证线程安全,如何取舍3.小结一下单例模式与线程安全问题踩的坑下...
    99+
    2024-04-02
  • linux该如何改名
    本篇文章为大家展示了linux该如何改名,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。方法:1、利用“vim /etc/hostname”命令修改主机名;2、利用“mv”命令给文件重命名,语法为“m...
    99+
    2023-06-29
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作