Linux多线程

Linux多线程

      • Linux 多线程
        • 一、Linux 线程概念
          • 什么是线程
          • Linux 没有真正的线程
        • 二、二级页表
          • 二级页表的工作原理
          • 修改常量字符串为何触发段错误?
        • 三、线程的优缺点
          • 线程的优点
          • 线程的缺点
        • 四、线程异常与用途
          • 线程异常
          • 线程用途
        • 五、Linux 进程与线程
          • 进程和线程的区别
          • 线程私有数据
          • 线程共享数据
          • 进程和线程的关系
        • 六、Linux 线程控制(POSIX 线程库)
          • 线程创建
          • 线程等待
          • 线程终止
          • 分离线程
          • 线程 ID 及地址空间布局
        • 七、总结


Linux 多线程

在 Linux 系统中,线程是一个非常重要的概念,它与进程密切相关,但又有其独特之处。本文将从线程的基本概念出发,深入探讨 Linux 中的线程实现、控制机制、优缺点、用途,以及相关的技术细节,如二级页表和 POSIX 线程库(pthread)的使用。


一、Linux 线程概念
什么是线程

线程(thread)是一个程序中的执行路线,更准确地说,它是“进程内部的一个控制序列”。每个进程至少包含一个执行线程,即主线程。线程运行在进程的地址空间内,依赖进程的资源运行。在 Linux 中,线程的本质是轻量级进程(Light Weight Process,LWP),由内核中的 task_struct 结构表示。CPU 将 task_struct 视为调度单位,而不区分它是传统意义上的进程还是线程。

当创建一个进程时,会伴随着进程控制块(task_struct)、进程地址空间(mm_struct)和页表的创建,虚拟地址通过页表映射到物理地址。而线程的创建则更为轻量化:只需新建一个 task_struct,并让其与父进程共享地址空间和页表。这样,多个线程共享进程的资源,形成多个执行流。

因此,Linux 中的进程可以分为:

  • 单执行流进程:只有一个 task_struct,即单线程进程。
  • 多执行流进程:包含多个 task_struct,即多线程进程。

从内核角度看,进程是系统资源分配的基本单位,而线程是调度的基本单位。CPU 只关心独立的 task_struct,并不区分它是进程还是线程,这使得 Linux 的线程实现比传统进程更轻量。

Linux 没有真正的线程

与 Windows 等支持原生线程的操作系统不同,Linux 并不存在真正意义上的线程,而是通过轻量级进程模拟线程。内核使用 task_struct 统一描述进程和线程,避免了为线程设计独立的控制结构。这种设计降低了操作系统的复杂性,但也意味着 Linux 中没有真正的线程相关系统调用。用户层通过 POSIX 线程库(pthread)模拟线程功能,而内核仅提供轻量级进程的创建接口,例如 vfork

例如,vfork 函数创建一个子进程并与父进程共享地址空间,其行为类似于线程:

#include <stdio.h>
#include <unistd.h>
int g_val = 100;
int main() {pid_t id = vfork();if (id == 0) {g_val = 200;printf("child: g_val=%d\n", g_val);exit(0);}sleep(3);printf("father: g_val=%d\n", g_val);return 0;
}

运行结果显示,子进程修改的全局变量 g_val 影响了父进程,证明它们共享地址空间。


二、二级页表

线程运行在进程的地址空间内,而地址空间的映射依赖页表。以 32 位平台为例,虚拟地址空间有 2³² 个地址(4GB)。如果使用单级页表,每个虚拟地址需映射到物理地址,假设每个表项占 10 字节(包括权限信息),则页表大小为 40GB,远超 32 位系统的内存容量(4GB)。因此,Linux 采用多级页表,其中 32 位平台使用二级页表。

二级页表的工作原理
  1. 页目录(一级页表):虚拟地址的前 10 位在页目录中查找,定位到对应的二级页表。
  2. 页表(二级页表):接下来的 10 位在二级页表中查找,确定物理页框的起始地址。
  3. 偏移量:最后 12 位作为页内偏移量,定位到具体的物理地址。

每个物理页框大小为 4KB(2¹² 字节),页目录和页表各有 2¹⁰ 个表项(1024 项),每项 10 字节,总大小约为 10MB,大幅降低了内存需求。映射过程由 CPU 内的内存管理单元(MMU)硬件完成,结合软件页表实现虚拟地址到物理地址的转换。

在 64 位平台下,Linux 使用更复杂的多级页表,以适应更大的地址空间。

修改常量字符串为何触发段错误?

常量字符串位于只读数据段,页表将其标记为只读。当尝试修改时,MMU 检测到权限冲突,触发硬件异常,操作系统随即向进程发送信号(如 SIGSEGV),终止进程。


三、线程的优缺点
线程的优点
  1. 创建开销小:创建线程比创建进程更快,因为无需复制地址空间。
  2. 切换效率高:线程切换仅涉及上下文切换,不需要更改页表。
  3. 资源占用少:线程共享进程资源,节省内存。
  4. 并行性强:多线程可充分利用多核处理器。
  5. 异步执行:在等待慢速 I/O 时,其他线程可继续计算。
  6. 任务分解:计算密集型任务可拆分为多线程并行执行;I/O 密集型任务可重叠 I/O 操作。
线程的缺点
  1. 性能损失:计算密集型线程过多时,调度和同步开销增加。
  2. 健壮性低:线程间缺乏隔离,一个线程崩溃可能导致整个进程终止。
  3. 缺乏访问控制:线程共享资源,调用某些 OS 函数可能影响整个进程。
  4. 编程复杂:多线程程序调试困难,容易出现竞争条件或死锁。

四、线程异常与用途
线程异常

线程可能因除零、野指针等异常崩溃。由于线程是进程的执行分支,其崩溃会触发信号机制,终止整个进程。例如:

void* routine(void* arg) {int a = 1 / 0; // 除零异常return NULL;
}

线程崩溃后,进程随之退出,所有线程停止运行。这体现了多线程健壮性较低的缺点。

线程用途
  1. 提升效率:多线程并行处理 CPU 密集型任务。
  2. 改善体验:I/O 密集型任务(如下载文件时同时编辑代码)通过多线程实现异步操作。

五、Linux 进程与线程
进程和线程的区别
  • 进程:资源分配的基本单位,拥有独立的地址空间。
  • 线程:调度的基本单位,共享进程的地址空间。
线程私有数据

尽管线程共享进程资源,但每个线程拥有:

  • 线程 ID(pthread_t)
  • 寄存器组(上下文)
  • 栈空间
  • errno 变量
  • 信号屏蔽字
  • 调度优先级
线程共享数据

线程共享进程的:

  • 代码段和数据段(如全局变量、函数)
  • 文件描述符表
  • 信号处理方式
  • 当前工作目录
  • 用户 ID 和组 ID
进程和线程的关系

进程是一个容器,线程是其中的执行流。单线程进程只有一个执行流,多线程进程包含多个执行流。


六、Linux 线程控制(POSIX 线程库)

Linux 通过 POSIX 线程库(pthread)在用户层实现线程功能。pthread 封装了轻量级进程的系统调用,提供线程创建、等待、终止等接口。使用时需包含 <pthread.h> 并链接 -lpthread

线程创建

pthread_create 创建新线程:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
  • thread:输出参数,存储新线程 ID。
  • attr:线程属性,NULL 表示默认。
  • start_routine:线程执行函数。
  • arg:传递给线程的参数。

示例:

#include <stdio.h>
#include <pthread.h>
void* routine(void* arg) {while (1) {printf("New thread: %s\n", (char*)arg);sleep(1);}return NULL;
}
int main() {pthread_t tid;pthread_create(&tid, NULL, routine, "thread 1");while (1) {printf("Main thread\n");sleep(2);}return 0;
}

运行后,主线程和新线程交替打印,共享同一进程的 PID。

线程等待

pthread_join 等待线程结束:

int pthread_join(pthread_t thread, void **retval);
  • thread:待等待的线程 ID。
  • retval:线程退出码。

如果不等待,线程退出后资源不会释放,可能导致内存泄漏。示例:

void* routine(void* arg) {sleep(5);return (void*)2022;
}
int main() {pthread_t tid;pthread_create(&tid, NULL, routine, NULL);void* ret;pthread_join(tid, &ret);printf("Exit code: %d\n", (int)ret); // 输出 2022return 0;
}

线程异常时(如除零),进程崩溃,pthread_join 无法执行,因此只能获取正常退出时的退出码。

线程终止
  1. return:从线程函数返回,终止当前线程。
  2. pthread_exit:主动退出线程:
    void pthread_exit(void *retval);
    
  3. pthread_cancel:取消其他线程:
    int pthread_cancel(pthread_t thread);
    

取消的线程退出码为 PTHREAD_CANCELED(-1)。

示例(主线程取消新线程):

void* routine(void* arg) {while (1) {printf("Running\n");sleep(1);}
}
int main() {pthread_t tid;pthread_create(&tid, NULL, routine, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, NULL);printf("Thread canceled\n");return 0;
}
分离线程

pthread_detach 将线程设置为分离状态,退出时自动释放资源:

int pthread_detach(pthread_t thread);

示例:

void* routine(void* arg) {pthread_detach(pthread_self());sleep(5);return NULL;
}
int main() {pthread_t tid;pthread_create(&tid, NULL, routine, NULL);sleep(10); // 无需 joinreturn 0;
}
线程 ID 及地址空间布局

pthread_t 是用户级线程 ID,表示进程地址空间共享区中描述线程的内存块地址(NPTL 实现)。通过 pthread_self 获取当前线程 ID,与 pthread_create 的输出参数一致,但不同于内核的 LWP ID。

线程栈:

  • 主线程使用进程原生栈。
  • 新线程在共享区分配栈空间。

七、总结

Linux 通过轻量级进程模拟线程,利用 task_struct 和共享地址空间实现高效的多线程机制。POSIX 线程库提供了用户友好的接口,支持线程创建、管理和终止。线程的优点在于轻量和并行性,但缺点包括健壮性低和编程复杂性高。理解线程与进程的关系、二级页表的工作原理以及 pthread 的使用,能帮助开发者更好地利用多线程优化程序性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38765.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

资金管理策略思路

详细描述了完整交易策略的实现细节&#xff0c;主要包括输入参数、变量定义、趋势判断、入场与出场条件、止损与止盈设置等多个方面。 输入参数&#xff08;Input&#xff09;&#xff1a; EntryFrL (.6)&#xff1a;多头入场的前一日波动范围的倍数。 EntryFrS (.3)&#xff1…

体育直播视频源格式解析:M3U8 vs FLV

在体育直播领域&#xff0c;视频源的格式选择直接影响着直播的流畅度、画质以及兼容性。目前&#xff0c;M3U8 和 FLV 是两种最为常见的视频流格式&#xff0c;它们各有优劣&#xff0c;适用于不同的场景。本文将从技术原理、优缺点以及应用场景等方面对 M3U8 和 FLV 进行详细解…

【动态规划】下降路径最小和

跟之前不同由于可能取到最右上角值&#xff0c;则左右各加一列&#xff0c;并且由于求最小值&#xff0c;则加的列须设置为正无穷大&#xff1b; class Solution { public:int minFallingPathSum(vector<vector<int>>& matrix) {int nmatrix.size();vector<…

07_GRU模型

GRU模型 双向GRU笔记:https://blog.csdn.net/weixin_44579176/article/details/146459952 概念 GRU&#xff08;Gated Recurrent Unit&#xff09;也称为门控循环单元&#xff0c;是一种改进版的RNN。与LSTM一样能够有效捕捉长序列之间的语义关联&#xff0c;通过引入两个&qu…

VScode

由于centos停止了维护 ,后面使用ubuntu 在Ubuntu中用vscode 充当记事本的作用 替代了centos中vim的作用 后面使用vscode编辑 vscode中继续使用makefile , xshell中的cgdb进行debug (半图形写 ,半命令行debug&&运行) 官网下载地址&#xff1a;https://code.visuals…

【行驶证识别】批量咕嘎OCR识别行驶证照片复印件图片里的文字信息保存表格或改名字,基于QT和腾讯云api_ocr的实现方式

项目背景 在许多业务场景中,如物流管理、车辆租赁、保险理赔等,常常需要处理大量的行驶证照片复印件。手动录入行驶证上的文字信息,像车主姓名、车辆型号、车牌号码等,不仅效率低下,还容易出现人为错误。借助 OCR(光学字符识别)技术,能够自动识别行驶证图片中的文字信…

异步编程与流水线架构:从理论到高并发

目录 一、异步编程核心机制解析 1.1 同步与异步的本质区别 1.1.1 控制流模型 1.1.2 资源利用对比 1.2 阻塞与非阻塞的技术实现 1.2.1 阻塞I/O模型 1.2.2 非阻塞I/O模型 1.3 异步编程关键技术 1.3.1 事件循环机制 1.3.2 Future/Promise模式 1.3.3 协程&#xff08;Cor…

python-selenium 爬虫 由易到难

本质 python第三方库 selenium 控制 浏览器驱动 浏览器驱动控制浏览器 推荐 edge 浏览器驱动&#xff08;不容易遇到版本或者兼容性的问题&#xff09; 驱动下载网址&#xff1a;链接: link 1、实战1 &#xff08;1&#xff09;安装 selenium 库 pip install selenium&#…

前端OOM内存泄漏如何排查?

前言 现代前端开发中&#xff0c;随着应用的复杂性和交互性的增加&#xff0c;OOM&#xff08;Out Of Memory&#xff0c;内存不足&#xff09;问题和内存泄漏逐渐成为影响用户体验和应用性能的关键挑战。排查和解决这些问题需要开发人员具备良好的调试技巧和优化策略。 造成…

C++20:玩转 string 的 starts_with 和 ends_with

文章目录 一、背景与动机二、string::starts_with 和 string::ends_with&#xff08;一&#xff09;语法与功能&#xff08;二&#xff09;使用示例1\. 判断字符串开头2\. 判断字符串结尾 &#xff08;三&#xff09;优势 三、string_view::starts_with 和 string_view::ends_w…

Redis、Memcached应用场景对比

环境 Redis官方网站&#xff1a; Redis - The Real-time Data Platform Redis社区版本下载地址&#xff1a;Install Redis | Docs Memcached官方网站&#xff1a;memcached - a distributed memory object caching system Memcached下载地址&#xff1a;memcached - a dis…

【MySQL】日志

目录 基本概念错误日志二进制日志查询日记慢查询日志 基本概念 日志&#xff08;Log&#xff09;是系统、软件或设备在运行过程中对发生的事件、操作或状态变化所做的记录。这些记录通常包含时间戳、事件类型、相关数据等信息&#xff0c;用于跟踪运行过程、排查故障、审计操作…

ArkUI-List组件

列表是一个复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;使得列表内容超出其范围的时候&#xff0c;就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用&#xff0c;条件渲染&#xff0c;循环渲染&#xff0c;懒加载等渲染控制方式生成子组件…

Word限定仅搜索中文或英文引号

在Word中&#xff0c;按下CtrlF键&#xff0c;左侧会弹出导航搜索栏&#xff1b; 点击放大镜旁边的下拉栏&#xff0c;选择高级查找 在查找内容处输入英文状态下的"&#xff0c;然后选择更多->使用通配符&#xff0c;就可以仅查找英文状态下的" 同理&#xff…

智能飞鸟监测 守护高压线安全

飞鸟检测新纪元&#xff1a;视觉分析技术的革新应用 在现代化社会中&#xff0c;飞鸟检测成为了多个领域不可忽视的重要环节。无论是高压线下的安全监测、工厂内的生产秩序维护&#xff0c;还是农业区的作物保护&#xff0c;飞鸟检测都扮演着至关重要的角色。传统的人工检测方…

React初学分享 事件绑定 组价通信 useState useEffect

React初学 React介绍快速搭建React项目JSXJSX的本质优势&#xff1a;JSX中使用JS表达式JSX中的列表渲染JSX实现简单条件渲染JSX实现复杂条件渲染 React中的事件绑定React基础事件绑定传递自定义参数同时传递事件对象和自定义参数 React中的组件useState修改状态的规则状态不可变…

【实战】deepseek数据分类用户评论数据

在平时的工作中&#xff0c;我们会遇到数据分类的情况&#xff0c;比如将一些文本划分为各个标签。如果人工分类这块的工作量将是非常大&#xff0c;而且分类数据的准确性也不高。我们需要用到一些工具来实现。提高效率的同时也提高准确率。 1.示例数据 用户ID 时间戳 评论场…

git tag以及git

git tag 以及git 一、先说收获吧 1. git bash 在windows上 类似于linux的bash提供的shell命令行窗口&#xff0c;可以执行很多linux命令&#xff0c;cd pwd ls vim cat touch mkdir&#xff0c;还可以用正则匹配查看标签。相当于在windows上装了一个小的linux。git init myproj…

[动手学习深度学习]28. 批量归一化

当前所有的深度学习网络&#xff0c;或多或少都用了批归一化操作 批归一化的思想不新&#xff0c;但是这个特定的层是16年左右出现的&#xff0c;在这之后&#xff0c;发现他对深度学习算法性能的提升非常有效 概念理解 这是一个网络的结构&#xff1a; 当数据很深的时候&am…

AI比人脑更强,因为被植入思维模型【17】万物联系思维模型

万物联系,万物,并不孤立。 定义 万物联系思维模型是一种强调世界上所有事物都相互关联、相互影响的思维方式。它认为任何事物都不是孤立存在的,而是与周围的环境、其他事物以及整个宇宙构成一个有机的整体。这种联系不仅包括直接的因果关系,还涵盖了间接的、潜在的、动态的…