Linux初阶——线程(Part1)
一、线程概念
1、如何理解线程
说到线程,那么我们就要回到进程了。
1.1. 再谈进程
对一个进程来说,它在内存中是这样的:

其中一个 task_struct 独享一个进程地址空间和一个页表。 而线程其实和进程差不多,是这样的:

其中多个 task_struct 共享一个进程地址空间,即共享进程地址空间里的正文代码、堆、变量等。所以,其实线程就是上图的一个个 task_struct , 然后这一个个 task_struct 分别执行进程代码的不同部分。所以也可以说因为进程的执行是由若干个线程的执行组成的。而也正因为线程执行的代码比进程的要少得多,因此我们称线程的执行粒度比进程要细得多。
所以,那真正的进程是什么样的呢?其实是这样的:

但是我们或许会产生这样一个疑问:那是不是以前进程的那些定义都错了呢,一个进程不是只有一个 PCB 吗,而在 Linux 里不就叫 task_struct 吗?其实这两者是不矛盾的,其实在只有一个 task_struct 的情况下,这个 task_struct 也是一个线程,只不过因为只有它一个,因此刚好独享了进程地址空间的全部资源罢了,所以进程的图就是图 1.1-a 样子了。所以,其实图 1.1-c 才是更普适的情况。
1.2. 理解线程
如果我们新建一个进程(包括 fork 一个子进程),那么操作系统就要为这个新进程开辟新的 task_struct、进程地址空间和页表。而如果我们新建一个线程,那么操作系统就只需要创建一个新的 task_struct 就可以了,然后和旧的 task_struct 一起共享页表和进程地址空间,然后不同的 task_struct 各自执行不同部分的代码。
所以,新建一个线程,操作系统除了会开辟空间当作 task_struct 外,就不会额外开辟其他空间了;但是新建一个进程,操作系统就会开辟新的 task_struct、进程地址空间和页表,简称新的系统资源。所以我们可以得出一个结论:操作系统执行代码是通过一个一个线程执行的,但操作系统只会对一个个进程分配系统资源(内存),并不会对一个个线程分配系统资源(内存)。
1.3. 线程与进程的关系
因为进程的执行是由若干个线程的执行组成的,但是操作系统是对进程发信号的;因此如果其中一个线程报错发信号,那么就会导致整个进程退出。那么既然整个进程都退出了,那进程里的全部线程绝对也全都退出了。所以我们可以得出一个结论:只要有一个线程挂了,那么整个进程里的线程也会全部挂掉。
2、二级页表(32 位)
当机器字长为 32 位时,内存里的地址只能用 32 位表示。但是我们知道如果进程地址空间只用了一级页表储存映射关系的话,而且进程地址空间总共是 4GB,因此页表的总大小也会是 4GB。可是这只是一个进程的,而且只是页表的大小,还没算代码和数据呢,如果算上这些的话,那么一个进程所占的空间就太大了,操作系统是不可能支持几十个进程载入内存的。所以如何缩小页表的大小呢?那么我们就要说说二级页表了。
2.1. 重新看待 32 位地址号
二级页表对 32 位地址号进行了拆分,采用了 10-10-12 的格式。高 10 位是指在哪一个二级页表;中 10 位是指在这个二级页表的哪一个内存块;而低 12 位是指这个内存块内的哪一行。

举个例子,如果地址为 0000000111 0000001000 000100101100,那么就说明要访问第 7 号二级页表中的第 8 号个内存块的第 300 号行。注意,所有的编号都是从零开始的。
2.2. 回到二级页表
得益于对 32 位地址的分段解读,虽然全部的二级页表的总行数还是和原来不用二级页表时的页表一样多;但通过二级页表,我们可以把原来的一级页表分成若干段,然后需要时就只把那一段的表加载进内存就行了;而不用像原来那样把整张一级页表加载进内存。这样就可以大幅减少页表所占用的内存空间了。
同时我们还可以发现,正是因为用了 10 位来表示表(一级页表和二级页表)中的编号,使得每个表的大小都是 4KB——刚好可以用一个内存块就可以装满了。因此操作系统开任何空间的大小都是按 4KB 的大小来开的,只不过我们可以用到的就不一定是 4KB 了。
3、线程周边概念
3.1. 执行流
因为 Linux 中,CPU 只认 task_struct,并不会认是不是进程或是不是线程,因此操作系统是没有进程或线程这一概念的,它只认 task_struct ,因此一个 task_struct 就是一个执行流了。
3.2. 主线程 & 新线程
其中 LWP(轻量型进程)等于 PID 的那个线程就是主线程,其他都是新线程。
二、线程的控制
1、线程的创建
1.1. clone 函数
参数介绍
- fn:新线程要执行那部分的代码的起始地址。
- stack: 该线程的线程栈,用于存储该线程的数据和变量,以及该线程的函数的栈帧。
这个函数是用来新建线程的,就如同 fork 函数一样。 但是,由于这个函数的参数是在太复杂了,因此线程库就把这个函数封装了起来;即在 pthread_create 函数和 pthread_join 函数的内部调用 clone 函数。
1.2. pthread_create 函数
参数介绍
- thread:输出型参数。把线程 ID 带出来。
- attr:输入型参数。输入该线程的属性,如果不输入就用默认属性。
- start_routine:就是 clone 函数的 fn。
- arg:输入型参数。输入 start_routine 函数的实参。
这个函数就是用来创建线程的。成功返回 0,失败返回其他值。
1.3. 线程 ID(pthread_t)
1.3.1. 为什么线程库要维护线程的概念
为什么线程库只要维护线程的概念,而不用维护线程的执行流(task_struct)?因为用户在用线程时,就指想获取线程有关的属性,比如线程 ID、线程的时间片、回调函数、独立栈等;但并不需要关注 task_struct 的编号、PID等那些 task_struct 的信息;因此线程库就没必要包含与 task_struct 的属性有关的东西了(不用描述 task_struct 了),取而代之的是,为了实现线程与执行流的概念,线程库中会把 tcb 与执行流建立连接,然后当线程要执行它的代码时,操作系统直接调用它的执行流就行了,但值得注意的是,此时用户并不关心操作系统调用的执行流,只会关心线程的属性!!!
1.3.2. 线程栈
由 clone 函数可知,创建一个线程时,不仅需要执行代码的起始地址,还要传一个栈的地址进去。而这个栈就是线程栈。
1.3.3. 线程 tid & 线程控制块 tcb
因为 Linux 是没有线程的概念的,只有 task_struct 的概念;因此在线程库内部就要维护线程的概念,而要维护线程的概念,就必须对多个线程进行管理,于是就要对线程进行“先描述,再组织”。而如何描述一个线程呢?其实 clone 函数和 pthread_create 函数就已经给出答案了:每个线程都要有自己的 ID,以及其他属性,还有自己的独享的栈。但这些都是在库内部维护的,因此如果要访问这些线程,那么就要把库加载到内存;但线程库是动态库,因此经过页表映射后是映射到进程地址空间的共享区里的;而线程这些概念是线程库在描述和组织,是线程库里的代码,因此线程的结构体和组织线程的数据结构、以及线程的栈都被加载到共享区里了,而不是进程地址空间的栈里。但主线程非常特殊,它的栈就是进程地址空间里的栈。
因为一个线程是由线程的属性(权限、时间片等)和线程栈组成的,因此在描述线程的结构体 tcb 肯定也是有这几个成分的。因此 tcb 的结构大概长这样:
而 tcb 的地址就是这个线程的 tid。
1.3.4. 线程的局部存储—— __thread 关键字
我们知道,全部线程是可以访问全局变量的。如果我线程想要一个私有的全局变量呢?那就要提到 __thread 关键字了。
__thread int count = 0; // 每个线程都有一个私有的 count 全局变量
以上面的例子为例,有了 __thread 之后,count 变量就不存在进程地址空间的数据区里了,而是存在每个线程 TCB 的线程局部存储区里了。
或许我们会想:那我在执行流的函数里定义个 count 变量不也一样吗?至于用这个私有的全局变量吗?其实,这个私有的全局变量同时适用于一个线程要执行多个函数这种情况。在这种情况下,该线程调用的所有函数都可以访问到这个 count;但如果只在执行流的函数里定义个 count 变量,该执行流调用的其他函数是无法访问这个 count 的。
注意:__thread 只能用于内置类型。
2、线程的回收
2.1. pthread_join 函数
参数介绍
- thread:输入型参数。线程的 tid
- value_ptr: 输出型参数。用来获取线程执行的函数的结果。如何获取呢?一般都会把线程执行的函数先强转成(void*)指针再返回。然后在给这个形参传 void* 指针变量的地址。
- 返回值:成功返回 0,错误返回其他值。
举个例子:
void* thread_run(void* arg)
{return (void*)100;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, thread_run, nullptr);void* ret_val;pthread_join(tid, &ret_val);int ret = static_cast<int>(ret_val);return 0;
}
这样就可以获取线程执行的函数的结果啦!
2.2. 线程分离—— pthread_detach 函数
在默认情况下,新建的线程的释放都要显式调用 pthread_join 函数。但如果我们不关心线程运行的结果,我们可以在线程执行的函数调 pthread_detach 函数,告诉系统当线程执行完后可以自动回收线程。于是我们就不用显式地调用 pthread_join 函数来释放线程了。
参数介绍
- thread:线程的 tid。
- 返回值:成功返回 0,失败返回其他值。
相关文章:

Linux初阶——线程(Part1)
一、线程概念 1、如何理解线程 说到线程,那么我们就要回到进程了。 1.1. 再谈进程 对一个进程来说,它在内存中是这样的: 图1.1-a 其中一个 task_struct 独享一个进程地址空间和一个页表。 而线程其实和进程差不多,是这样的&…...

SpringBoot后端开发常用工具详细介绍——flyway数据库版本控制工具
文章目录 什么是flyway简介为什么要使用flyway 流程介绍整合springboot添加pom文件配置flyway向resource/db/migration添加sql文件 注意事项1. 迁移报错2. 迁移顺序 参考 什么是flyway 简介 为什么要使用flyway 我们在开发时往往会有这样一种情况: 进行软件开发…...

CSS揭秘:7. 伪随机背景
前置知识:CSS 渐变,5. 条纹背景,6. 复杂的背景图案 前言 本篇主要内容依然是关于背景的,无限平铺的背景会显得整齐美观,但又有些呆板,如何实现背景的多样性和随机性,是本篇的核心。 一、四种颜…...

SAP CODE DEMO:查找AL11 指定路径下文件中的内容
有时候需要查找某个具体的内容,在哪个文件内。数据量大的时候可以利用程序查找 选择界面: 路径,和文件名都可以模糊搜查 search string:你要查找的信息。 代码参考如下: report z00R010 NO STANDARD PAGE HEADING…...
【华为HCIP实战课程二十四】中间到中间系统协议IS-IS配置实战,网络工程师
一、IS-IS整体架构 将Level-1路由器部署在非骨干区域,Level-2路由器和Level-1-2路由器部署在骨干区域。 每一个非骨干区域都通过Level-1-2路由器与骨干区域相连! 1、在IS-IS中,每个链路可以属于不同的区域,OSPF中每个链路属于同一个区域 2、在IS-IS中,单个区域没有物理…...
【工具】新手礼包之git相关环境包括中文的一套流程{收集和整理},gitlab的使用
【工具】新手礼包之git相关环境包括中文的一套流程{收集和整理} git Git 详细安装教程(详解 Git 安装过程的每一个步骤) TortoiseGit 【TortoiseGit】TortoiseGit安装和配置详细说明...
篇章十一 打包构建工具
文章目录 一、gulp1. 流2. gulp 的作用3. gulp 的安装、检测和卸载 二、webpack1. 打包样式资源2. 打包 html 资源3. 打包图片资源4. 压缩 html 代码5. 生产环境基本配置 三、vite 打包构建工具,都是依赖于 node 环境进行开发,底层封装的内容就是 node 里…...
青少年编程与数学 02-002 Sql Server 数据库应用 06课题、数据库操作
青少年编程与数学 02-002 Sql Server 数据库应用 06课题、数据库操作 课题摘要:一、数据库的文件组成二、系统数据库三、创建数据库四、数据库配置1. 修改数据库文件大小和增长设置2. 添加或移除数据文件3. 设置数据库选项4. 配置数据库的恢复模型5. 管理数据库的访问权限6. 使…...

MacOS下载安装Logisim(图文教程)
本章教程主要介绍如何在MacOS系统中安装Logisim。 一、Logisim是什么? Logisim是一个用于电子逻辑门电路模拟的教育工具软件。它允许用户通过图形界面构建和测试复杂的数字逻辑电路,如加法器、解码器、编码器、寄存器、内存等,从而帮助学生理解计算机硬件的工作原理。 二、如…...

Flink CDC系列之:调研应用Flink CDC将 ELT 从 MySQL 流式传输到 StarRocks方案
Flink CDC系列之:调研应用Flink CDC将 ELT 从 MySQL 流式传输到 StarRocks方案 准备准备 Flink Standalone 集群准备 docker compose为 MySQL 准备记录使用 Flink CDC CLI 提交作业 同步架构和数据更改路由变更清理 本教程将展示如何使用 Flink CDC 快速构建从 MySQ…...

一次元空间FullGC导致OOM问题分析
原文,作者:kkyeer 原文需要翻墙,所以转载。 现象 观测平台告警:FullGC次数大于阈值,5分钟内大于11次,频次大概1-2周有一次 告警后服务概率性会自动恢复,控制台打印 Exception: java.lang.OutOf…...
Web前端开发工具和依赖安装
各种安装: node.js https://nodejs.org/zh-cn/ 安装完node.js 可以使用npm,npm跟随nodejs一起安装 node --version 查看已安装node.js的版本,确认是否安装nodejs npm -v 查看npm版本npm install <Module Name> 安装模块 npm insta…...

【学习心得】远程root用户访问服务器中的MySQL8
一、Ubuntu下的MySQL8安装 在Ubuntu系统中安装MySQL 8.0可以通过以下步骤进行1. 更新包管理工具的仓库列表: sudo apt update 2. 安装MySQL 8.0,root用户默认没有密码: sudo apt install mysql-server sudo apt install mysql-client 【…...

lust变频器维修电梯变频器CDD34.014.W2.1LSPC1
LUST伺服在安装时须注意,不可有任何的铁屑、螺丝、导线等掉人驱动器内。在安装完成后应作基本的检测动作,如对地阻抗,和短路检测等。 所有的安装及使用事项需要符合安全规定,并且也需要符合当地的相关规定和灾害预防措施。DC BUS…...

跨越地域限制:在线原型设计软件的自由与便捷
网络原型设计软件因其便捷性和灵活性,在现代设计工作中扮演着至关重要的角色。与传统的桌面端软件相比,网络原型设计工具无需安装,不受地域限制,且兼容各种操作系统,无论是Linux、Solaris、Mac还是Windows,…...

flash-waimai:高仿饿了么外卖平台,使用他轻松打造自己的外卖平台
嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目和工作学习方法 flash-waimai 是一个完整的外卖平台解决方案,包括手机端、后台管理端和 API 服务。该项目仿照了饿了么的外卖服务,为用户提供了一个…...

2.5 塑性力学—应变状态
个人专栏—塑性力学 1.1 塑性力学基本概念 塑性力学基本概念 1.2 弹塑性材料的三杆桁架分析 弹塑性材料的三杆桁架分析 1.3 加载路径对桁架的影响 加载路径对桁架的影响 2.1 塑性力学——应力分析基本概念 应力分析基本概念 2.2 塑性力学——主应力、主方向、不变量 主应力、主…...

1.机器人抓取与操作介绍-深蓝学院
介绍 操作任务 操作 • Insertion • Pushing and sliding • 其它操作任务 抓取 • 两指(平行夹爪)抓取 • 灵巧手抓取 7轴 Franka 对应人的手臂 6轴 UR构型去掉一个自由度 课程大纲 Robotic Manipulation 操作 • Robotic manipulation refers…...

六,Linux基础环境搭建(CentOS7)- 安装HBase
Linux基础环境搭建(CentOS7)- 安装HBase 大家注意以下的环境搭建版本号,如果版本不匹配有可能出现问题! 一、HBase下载及安装 HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“…...

《计算机网络网络层:连接虚拟世界的关键桥梁》
一、网络层概述 网络层在计算机网络中占据着至关重要的地位,它作为连接不同网络的关键层次,起着承上启下的作用。网络层的主要任务是实现网络互连,将数据设法从源端经过若干个中间节点传送到目的端,为分组交换网上的不同主机提供通…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...