【Linux系统】线程:线程的优点 / 缺点 / 超线程技术 / 异常 / 用途

1、线程的优点
创建和删除线程代价较小
创建一个新线程的代价要比创建一个新进程小得多,删除代价也小。这种说法主要基于以下几个方面:
(1)资源共享
-
内存空间:每个进程都有自己独立的内存空间,包括代码段、数据段和堆栈等。当创建一个新的进程时,操作系统需要分配新的内存空间,并复制父进程的内存内容(这被称为地址空间复制)。而线程是进程内的执行单元,所有同属一个进程的线程共享该进程的内存空间,因此创建线程时不需要进行地址空间的复制。
-
系统资源:进程间不共享文件描述符、信号处理等系统资源,每个新进程都需要重新初始化这些资源。相比之下,同一进程内的多个线程可以共享文件描述符、信号处理器等资源。
(2)上下文切换
- 创建新进程涉及到完整的上下文环境设置,包括用户态和内核态的各种数据结构的初始化。而创建线程时,由于大多数资源都是共享的,只需要建立线程控制块(TCB)以及分配栈空间等少量操作即可,因此上下文切换的成本更低。
(3)性能开销
-
时间消耗:创建和销毁进程涉及大量的系统调用和内存操作,这通常需要更多的时间。线程由于减少了大量重复资源的分配与初始化工作,所以创建和销毁的速度更快,时间消耗更少。
-
内存消耗:新进程需要分配单独的内存空间,导致内存占用相对较高;而新线程仅需分配栈空间(通常是几MB),因此内存消耗远小于创建新进程所需的消耗。
线程切换 vs 进程切换:线程切换代价较小
更加具体和详细的整理讲解可以看这篇博客:
【Linux系统】线程切换 vs 进程切换 :线程切换成本较低-CSDN博客
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
(例如 CR3 寄存器存储页表的物理地址,线程切换不更换页表,则像 CR3 这类“共享的寄存器上下文就无需切换”)
另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲 TLB(快表)会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题,当然还有硬件 Cache。
意思是:多线程因为同属于同一个进程,线程之间可能会使用共享的数据或代码,因此 TLB 和 Cache 的缓存对其他线程也有帮助,而进程切换就一定要切换 TLB 和Cache 了,因为代码完全变了。
线程占用的资源要比进程少很多
同一进程内的所有线程共享该进程的大部分资源,包括内存地址空间、文件描述符、数据段等。
每个线程仅需要维护自己独立的线程控制块(TCB),这其中包括了线程的栈指针、程序计数器、寄存器集合等少量信息,因此线程占用的资源较少,切换速度也更快。
而进程之间的资源占用则多得多。每个进程都有自己的地址空间、文件描述符表、页表等,这些资源都是独立且私有的。这意味着进程之间的切换和通信开销更大,因为操作系统需要为每个进程分配独立的内存空间,并确保进程间的数据隔离与保护。
能充分利用多处理器的可并行数量。
每个处理器(这里考了单核处理器的情况)可调用一个线程,在多处理器系统下,本系统就可以同时调用多个线程,实现并行处理任务
其他优点
(1)在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务。
这个比较容易理解,例如有一个进程用于读取磁盘内容,磁盘 IO 属于慢速 IO,需要磁盘 IO 时,本进程可以创建一个线程专门用于磁盘 IO,可以创建其他线程用于其他工作任务,增加并行量,提升工作效率
(2)计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
例如某计算密集型应用创建一个进程用于密集型计算,需要计算十万条数据,就可以创建10个线程分别计算一万条数据,在多处理器系统上,每个处理器一次调用一个线程,这样就能在多处理器系统上将计算分解到多个线程中
(3)I/O 密集型应用,为了提高性能,将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作。
这种也一样,将 IO 任务分配给多个线程处理
问题:计算密集型应用,线程是否越多越好?
答:并不是,若线程过多,则问题就由计算问题,转变为调度问题,增加了其他线程的等待时间,同时线程之间的TCB切换等工作也会耗时
问题:计算密集型应用,那线程一般建议创建多少个?
答:CPU物理个数乘于核数,相当于在多个多核处理器系统上,能够将所有CPU核心都刚好运用上
理想情况:线程数 = CPU核心数
理论基础:在理想的计算密集型应用中,每个线程都应该绑定到一个独立的CPU核心上,以避免上下文切换和资源竞争。因此,线程的数量应等于系统中的物理核心数(或逻辑核心数,如果启用了超线程技术)。
公式:
单个处理器:线程数 = 物理核心数
多核处理器:线程数 = 物理核心数 × 处理器数量
超线程技术:如果启用了超线程(Hyper-Threading),则线程数 = 逻辑核心数 = 物理核心数 × 2
示例:
如果你有一个4核的处理器,且没有启用超线程,那么理论上你应该创建4个线程。
如果你有一个8核的处理器,并且启用了超线程,那么你应该创建16个线程(8核 × 2)。
问题:什么是超线程技术?
超线程技术,每个物理核心可以模拟出两个逻辑核心。理论上,你可以创建两倍于物理核心数的线程。
超线程技术(Hyper-Threading Technology, HTT) 是英特尔(Intel)开发的一种并行计算技术,旨在通过让每个物理CPU核心模拟出多个逻辑核心(通常为两个),从而提高多线程应用程序的性能。超线程技术的核心思想是利用单个物理核心中的闲置资源来同时执行多个线程,从而提高系统的整体效率。
- 物理核心 vs. 逻辑核心:
- 物理核心:每个物理核心是一个独立的处理单元,具备自己的算术逻辑单元(ALU)、寄存器、缓存等资源。
- 逻辑核心:在启用超线程的情况下,每个物理核心可以模拟出两个或更多的逻辑核心。操作系统会将这些逻辑核心视为独立的处理器,并可以为每个逻辑核心分配不同的线程。
- 资源共享:
- 在超线程技术中,物理核心的某些资源(如执行单元、缓存、浮点运算单元等)是共享的,而其他资源(如寄存器、指令队列等)则是为每个逻辑核心独立分配的。
- 这意味着,虽然每个逻辑核心可以独立执行线程,但它们仍然共享同一物理核心的大部分硬件资源。因此,超线程并不能真正增加物理核心的数量,而是通过更高效地利用现有的资源来提高性能。
这就是为什么我的笔记本电脑有8个内核,却有16个逻辑处理器的原因
拓展:多核处理器与多处理器系统
处理器等同于CPU吗?
在广义定义上,处理器是一个更为广泛的概念,指的是任何能够执行指令并处理数据的电子设备或组件。它不仅包括中央处理器(CPU),还可以指其他类型的处理器,如图形处理器(GPU,专门用于图形渲染和并行计算)、张量处理单元(TPU,专为机器学习任务设计的处理器)等。
多核处理器与CPU的关系
- 多核处理器:指在一个物理芯片上集成了多个CPU核心。每个核心都可以独立执行程序指令,相当于一个单独的CPU。因此,可以将多核处理器视为多个CPU核心集成在一起的处理器。
- CPU核心:每个CPU核心都是一个完整的计算单元,能够进行指令解码、执行运算以及访问内存等操作。
多核处理器和多处理器系统的区别
-
多核处理器:
- 在一个物理芯片上集成多个独立的CPU核心。
- 所有核心共享同一块物理芯片上的资源,例如内存控制器、总线等。
-
多处理器系统:
- 指一台计算机中安装了两个或更多的物理处理器(CPU芯片)。这些处理器可以是单核或多核的。
- 每个处理器(无论是单核还是多核)都有自己的缓存、内存控制器等资源,并且它们之间需要有效的通信机制来协调工作。
2、线程的缺点
- 性能损失
- 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低
- 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制
- 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
- 编写与调试一个多线程程序比单线程程序困难得多。这个主要是因为线程共享资源,容易起冲突,就需要程序员设置各种保护机制。
3、线程异常
(1)进程级别的致命信号触发
- 当一个线程因非法操作(如访问无效内存地址)导致触发致命信号(例如SIGSEGV),这会导致整个进程终止。这是因为大多数操作系统将这类信号视为进程级别的事件,而不是单独针对产生问题的线程。因此,一旦某个线程引发这种类型的崩溃,该进程下的所有线程都会被终止。
(2)未处理的算术和内存错误导致进程崩溃
- 如果单个线程执行了除零操作或使用了野指针,且这些行为未被捕获处理,则会引发运行时错误。在C/C++等语言中,这种情况通常会导致程序直接崩溃。如果此线程所在的进程没有设置适当的信号处理器来处理此类异常情况,那么该进程将会终止,同时其所有的线程也会随之结束。
(3)异常处理与信号机制
- 线程作为进程的执行分支,在出现异常时首先会在其自身的上下文中尝试处理。然而,对于一些无法恢复的严重错误(比如访问非法内存地址、除零等),如果没有有效的异常处理机制,会触发相应的信号(例如SIGFPE用于算术异常,SIGSEGV用于无效内存访问)。由于这些信号默认是发送给整个进程的,而非特定线程,因此会导致整个进程终止,包括其中的所有线程。
(4)注意事项
有人说:“一个线程崩溃,其他同进程下的所有线程都会被干掉,系统会给这些线程都发相同信号。”
这句话实际上包含了几个误解点:
错误点一:一个线程崩溃,其他同进程下的所有线程都会被干掉
纠正说明:
- 当一个线程因执行非法操作(例如访问无效内存地址或除零)导致崩溃时,并不是直接导致其他线程被“干掉”。真正发生的是该线程触发了一个致命信号(如SIGSEGV)。由于大多数操作系统将此类信号视为进程级别的事件,这意味着一旦某个线程引发了这种类型的错误,整个进程可能会因此终止。而当进程终止时,属于该进程的所有线程也会随之结束。
错误点二:系统会给这些线程都发相同信号
纠正说明:
- 实际上,在遇到上述情况时,信号是发送给整个进程的,而不是单独发送给每个线程。在Unix/Linux等操作系统中,许多关键信号(如SIGSEGV, SIGFPE)是针对整个进程的。如果一个线程触发了这样的信号且未被捕获处理,则该信号会导致整个进程终止,而非仅仅影响到产生问题的那个线程。换句话说,没有所谓的系统直接向其他线程发送相同的信号来“干掉”它们,而是进程的终止间接导致了所有线程的结束。
(5)系统如何干掉同进程下的所有线程?
是按照线程组为单位杀死线程的。
在进程里存在线程组的概念,线程组中包含了本进程下的所有线程,而线程组的ID值为TGID。主线程(也就是第一个启动的线程)的TID等于该线程组的TGID。当某个线程触发某些异常情况导致系统发送致命信号给此进程时,在进程退出前,系统会以线程组为单位,将属于该进程的所有线程一并清除并退出。这样做确保了整个进程要么完全运行,要么彻底停止,有效地避免了由于部分线程残留可能引发的问题。
(6)有没有发送给线程的信号
确实存在可以发送给线程的信号,但这种情况与发送给进程的信号有所不同。在Unix/Linux系统中,信号通常被设计为发送给整个进程。然而,随着多线程编程的发展,也引入了对特定线程发送信号的能力。
发送给线程的信号
-
pthread_kill() 函数:在支持POSIX线程(pthreads)的系统中,可以使用
pthread_kill()函数向特定的线程发送信号。这允许程序对特定线程进行更加精细的控制。例如,可以通过这种方式来中断一个长时间运行的线程而不影响整个进程。 -
线程特定信号处理:当信号发送到特定线程时,只有目标线程会调用其注册的信号处理程序。这意味着可以在不影响其他线程的情况下单独处理某个线程中的异常情况或其他需要关注的状态变化。
综上所述,虽然大多数情况下讨论的信号都是以进程为中心的,但在多线程环境中,确实也有直接发送给特定线程的信号,并且这种机制提供了更细粒度的控制能力。
4、线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
相关文章:
【Linux系统】线程:线程的优点 / 缺点 / 超线程技术 / 异常 / 用途
1、线程的优点 创建和删除线程代价较小 创建一个新线程的代价要比创建一个新进程小得多,删除代价也小。这种说法主要基于以下几个方面: (1)资源共享 内存空间:每个进程都有自己独立的内存空间,包括代码段…...
老榕树的Java专题:Redis 从入门到实践
一、引言 在当今的软件开发领域,数据的高效存储和快速访问是至关重要的。Redis(Remote Dictionary Server)作为一个开源的、基于内存的数据结构存储系统,因其高性能、丰富的数据类型和广泛的应用场景,成为了众多开发者…...
123,【7】 buuctf web [极客大挑战 2019]Secret File
进入靶场 太熟悉了,有种回家的感觉 查看源代码,发现一个紫色文件 点下看看 点secret 信息被隐藏了 要么源代码,要么抓包 源代码没有,抓包 自己点击时只能看到1和3处的文件,点击1后直接跳转3,根本不出…...
微服务知识——微服务拆分规范
文章目录 一、微服务拆分规范1、高内聚、低耦合2、服务拆分正交性原则3、服务拆分层级最多三层4、服务粒度适中、演进式拆分5、避免环形依赖、双向依赖6、通用化接口设计,减少定制化设计7、接口设计需要严格保证兼容性8、将串行调用改为并行调用,或者异步…...
docker数据持久化的意义
Docker 数据持久化是指在 Docker 容器中保存的数据不会因为容器的停止、删除或重启而丢失。Docker 容器本身是临时性的,默认情况下,容器内的文件系统是临时的,容器停止或删除后,其中的数据也会随之丢失。为了确保重要数据…...
双目标定与生成深度图
基于C#联合Halcon实现双目标定整体效果 一,标定 1,标定前准备工作 (获取描述文件与获取相机参数) 针对标准标定板可以直接调用官方提供描述文件,也可以自己生成描述文件后用PS文件打印 2,相机标定 &…...
【SQL】count(1)、count() 与 count(列名) 的区别
在 SQL 中,COUNT 函数用于计算查询结果集中的行数。COUNT(1)、COUNT(*) 和 COUNT(列名) 都可以用来统计行数,但它们在实现细节和使用场景上有一些区别。以下是详细的解释: 1. COUNT(1) 定义: COUNT(1) 计算查询结果集中的行数。 实现: 在执…...
使用bucardo实现postgresql数据库双主同步
使用bucardo实现postgresql数据库双主同步 方案优缺点 优点 pg数据库只支持单向数据复制,双机部署一般只能使用主(读写)备(只读)模式。而使用bucardo能实现pg数据库双机的双主模式,支持同时双写…...
在 Navicat 17 中扩展 PostgreSQL 数据类型 | 创建自定义域
定义域 以适当的格式存储数据可以确保数据完整性,防止错误,优化性能,并通过实施验证规则和支持高效数据管理来维护系统间的一致性。基于这些原因,顶级关系数据库(如PostgreSQL)提供了多种数据类型。此外&a…...
【Apache Paimon】-- 15 -- 利用 paimon-flink-action 同步 postgresql 表数据
利用 Paimon Schema Evolution 核心特性同步变更的 postgresql 表结构和数据 1、背景信息 在Paimon 诞生以前,若 mysql/pg 等数据源的表结构发生变化时,我们有几种处理方式 (1)人工消息通知,然后手动同步到数据仓库中(2)使用 flink 消费 DDL binlog ,然后自动更新 Hi…...
获取 ARM Cortex - M 系列处理器中 PRIMASK 寄存器的值
第一种实现(纯汇编形式) __ASM uint32_t __get_PRIMASK(void) {mrs r0, primaskbx lr }代码分析 __ASM 关键字:这通常是特定编译器(如 ARM GCC 等)用于嵌入汇编代码的指示符。它告诉编译器下面的代码是汇编代码。mrs …...
Linux+Docer 容器化部署之 Shell 语法入门篇 【Shell 替代】
🎀🎀Shell语法入门篇 系列篇 🎀🎀 LinuxDocer 容器化部署之 Shell 语法入门篇 【准备阶段】LinuxDocer 容器化部署之 Shell 语法入门篇 【Shell变量】LinuxDocer 容器化部署之 Shell 语法入门篇 【Shell数组与函数】LinuxDocer 容…...
如何处理网络连接错误导致的fetch失败?
处理由于网络连接错误导致的 fetch 失败通常涉及捕获网络错误并提供适当的用户反馈。以下是如何在 Vue 3 中实现这一点的步骤和示例。 一、更新 useFetch 函数 在 useFetch 函数中,需要捕获网络错误,并设置相应的错误信息。网络错误通常会抛出一个 TypeError,可以根据这个…...
PHP PDO 教程
PHP PDO 教程 概述 PHP PDO(PHP Data Objects)扩展为PHP提供了数据访问抽象层。PDO可以让你使用相同的接口访问多种数据库系统,这大大简化了数据库操作。本文将详细介绍PHP PDO的基本用法、优势以及在实际开发中的应用。 ##PDO 简介 PDO是…...
离线统信系统的python第三方库批量安装流程
一、关于UOS本机 操作系统:UOS(基于Debian的Linux发行版) CPU:海光x86 二、具体步骤 1、在联网的电脑上用控制台的pip命令批量下载指定版本的第三方库 方法A cd <目标位置的绝对路径> pip download -d . --platform many…...
IDEA+DeepSeek让Java开发起飞
1.获取DeepSeek秘钥 登录DeepSeek官网 : https://www.deepseek.com/ 进入API开放平台,第一次需要注册一个账号 进去之后需要创建一个API KEY,然后把APIkey记录保存下来 接着我们获取DeepSeek的API对话接口地址,点击左边的:接口…...
mysql的原理及经验
1. 存储引擎 存储引擎是MySQL的核心组件之一,它负责数据的存储和检索。MySQL支持多种存储引擎,每种引擎都有其独特的特点和适用场景。 InnoDB:这是MySQL的默认存储引擎,支持事务处理(ACID特性)、行级锁定和…...
苹果公司宣布正式开源 Xcode 引擎 Swift Build145
2025 年 2 月 1 日,苹果公司宣布正式开源 Xcode 引擎 Swift Build145。 Swift 是苹果公司于 2014 年推出的一种开源编程语言,用于开发 iOS、iPadOS、macOS、watchOS 和 tvOS 等平台的应用程序。 发展历程 诞生:2014 年,苹果在全球…...
怀旧经典:1200+款红白机游戏合集,Windows版一键畅玩
沉浸在怀旧的海洋中,体验经典红白机游戏的魅力!我们为您精心准备了超过1200款经典游戏的合集,每一款都是时代的印记,每一场都是回忆的旅程。这个合集不仅包含了丰富的游戏资源,还内置了多个Windows版的NES模拟器&…...
《解锁GANs黑科技:打造影视游戏的逼真3D模型》
在游戏与影视制作领域,逼真的3D模型是构建沉浸式虚拟世界的关键要素。从游戏中栩栩如生的角色形象,到影视里震撼人心的宏大场景,高品质3D模型的重要性不言而喻。随着人工智能技术的飞速发展,生成对抗网络(GANs…...
【漫话机器学习系列】083.安斯库姆四重奏(Anscombe‘s Quartet)
安斯库姆四重奏(Anscombes Quartet) 1. 什么是安斯库姆四重奏? 安斯库姆四重奏(Anscombes Quartet)是一组由统计学家弗朗西斯安斯库姆(Francis Anscombe) 在 1973 年 提出的 四组数据集。它们…...
kafka消费端之分区分配策略
文章目录 概述分区分配策略RangeAssignor分配策略RoundRobinAssignor分配策略StickyAssignor自定义分区分配策略 总结 概述 我们知道kafka的topic可以被分成多个分区,消费者在集群模式下消费时一个消费组内的每个消费者实例只能消费到一个分区的消息,那…...
e2studio开发RA2E1(9)----定时器GPT配置输入捕获
e2studio开发RA2E1.9--定时器GPT配置输入捕获 概述视频教学样品申请硬件准备参考程序源码下载选择计时器时钟源UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()printf输出重定向到串口定时器输入捕获配…...
【Elasticsearch】分桶聚合功能概述
这些聚合功能可以根据它们的作用和应用场景分为几大类,以下是分类后的结果: 1.基础聚合(Basic Aggregations) • Terms(字段聚合) 根据字段值对数据进行分组并统计。 例子:按产品类别统计销…...
开源安全一站式构建!开启企业开源治理新篇章
在如今信息技术日新月异、飞速发展的数字化时代,开源技术如同一股强劲的东风,为企业创新注入了源源不断的活力,然而,正如一枚硬币有正反两面,开源技术的广泛应用亦伴随着不容忽视的挑战。安全风险如影随形,…...
功能架构元模型
功能架构的元模型是对功能架构进行描述和建模的基础框架,它有助于统一不同团队对系统的理解,并为系统的设计和开发提供一致的标准和规范。虽然具体的元模型可能因不同的应用领域和特定需求而有所差异,但一般来说,功能架构的元模型可以涵盖以下几个方面: 组件/模块元模型:…...
Node.js 与 npm 版本兼容性问题详解:如何避免版本冲突
个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] 📱…...
鸿蒙开发中 SaveButton 按钮 保存按钮点击后权限授权失败。
原因分析 查看官方文档的解释 在 控制台中 过滤这个字段 过滤关键字"SecurityComponentCheckFail"可以获取具体原因。 得到 产生的原因 是 因为层叠的原因 savebutton 组件必须的 在屏幕的最高层 不能有任何的覆盖和遮挡 通过这样书写就解决了 // 下面是安…...
胜任力冰山模型:深入探索职业能力的多维结构
目录 1、序言 2、什么是胜任力? 3、任职资格和胜任力的区别 4、胜任力冰山模型:职场能力的多维展现 4.1、冰山水面上的部分 4.2、冰山水面下的部分 4.3、深层的个人特质与价值观 5、如何平衡任职资格与胜任能力 6、结语 1、序言 在快速发展的I…...
C#面试常考随笔12:游戏开发中常用的设计模式【C#面试题(中级篇)补充】
C#面试题(中级篇),详细讲解,帮助你深刻理解,拒绝背话术!-CSDN博客 简单工厂模式 优点: 根据条件有工厂类直接创建具体的产品 客户端无需知道具体的对象名字,可以通过配置文件创建…...
