Linux下的多线程

前面学习了进程、文件等概念,接下里为大家引入线程的概念

多线程

  • 线程是什么?
  • 为什么要有线程?
  • 线程的优缺点
  • Linux线程操作
    • 线程创建
    • 线程等待
    • 线程终止
    • 线程分离
  • 线程间的私有和共享数据
  • 理解线程库和线程id
  • 深刻理解Linux多线程(重点)

线程是什么?

  • 线程是一个执行分支,执行粒度比进程更细,调度成本更低。
  • 线程是进程内部的一个执行流
  • 线程是CPU调度的基本单位,进程是承担分配系统资源的基本实体

为什么要有线程?

多线程编程在现代计算中具有显著的优势,主要体现在以下几个方面:

  1. 发挥多核CPU优势:
    现代处理器普遍拥有多个核心,多线程能够确保这些核心得到充分利用。通过创建多个线程,程序可以同时在不同核心上执行不同的任务部分,从而提升并行处理能力,整体提高程序的运行效率。
  2. 防止阻塞和提高响应性:
    当一个线程在等待IO操作(如磁盘读写、网络通信)或执行耗时计算时,操作系统可以调度其他线程继续执行,避免了整个进程停滞,提高了系统的响应速度。
  3. 并发处理能力增强:
    多线程使得系统能够并发地处理多个用户请求或执行多个独立的任务。这对于高并发场景下的服务器应用、实时数据处理、大规模并行计算等尤为关键,可以显著提升系统吞吐量和服务质量。
  4. 模块化与简化设计:
    在复杂的软件架构中,多线程有助于将大的任务分解成若干个可管理的子任务,并分配给不同的线程来处理。这不仅使得代码逻辑更加清晰,也更容易进行模块化开发和维护。
  5. 改善用户体验:
    用户界面应用程序中,主线程负责处理UI事件,后台线程则可以处理长时间运行的操作(例如加载数据、文件压缩解压)。这样可以在不影响用户界面交互的同时完成大量工作,提升了用户体验。
  6. 资源利用率:
    相对于为每个并发任务创建新的进程,线程间的切换开销较小,因为它们共享同一进程的地址空间和其他资源。这降低了系统资源消耗,尤其是在内存有限的情况下。

然而,多线程编程也带来了一系列挑战,包括但不限于:资源共享引发的数据竞争问题、死锁、优先级反转等并发控制难题。因此,在享受多线程带来的性能提升时,开发者需要精心设计和实现线程间同步机制,以保证程序的正确性和稳定性。

线程的优缺点

优点:

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点:

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。
  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。(一个线程崩溃了,系统发送信号是以进程为单位的,所以整个进程都会崩溃。)
  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响(多线程共享地址空间)。

Linux线程操作

使用Linux线程接口的时候,我们需要导入线程库。例如:

g++ -o main main.cpp -std=c++11 -lpthread

这个pthread就是Linux自带的线程库,但是我们上面线程概念提到了Linux没有真正的线程,而是用进程模拟的线程(LWP),所以Linux也没有真正的线程接口,Linux提供的是轻量级进程的系统接口,然后对这个接口进行封装,封装成线程库,在用户的角度看这就是线程控制的接口,从而完成对线程的控制。(任何操作系统都会自带线程库)

线程创建

线程创建使用pthread_create这个函数接口,需要包含头文件pthread.h
在这里插入图片描述
参数解析:

  • pthread_t *thread //线程id
  • const pthread_attr_t *attr //设置线程属性,一般为nullptr
  • void *(start_routine) (void ) //回调函数,会执行传入的函数指针(返回值void,参数void
  • void *arg //可以作为函数参数

线程等待

在主线程调用这个函数会让主线程阻塞等待线程结束,并不是所有的情况都必须调用这个函数。
在这里插入图片描述
传入对应的线程id,并且阻塞等待。第二个参数是一个输出型参数,用于接收线程返回值。

线程终止

线程终止可能由以下几种情况造成:

  1. 一个线程正常情况下结束有可能是线程函数执行结束了,return void*。
  2. 还有一种就是调用这个函数,线程调用这个函数让自己退出。
    在这里插入图片描述
  3. 主线程调用这个函数就可以取消正在执行的线程。返回值为-1。
    在这里插入图片描述

线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

分离线程的接口,只需要传入线程id就可以了。
在这里插入图片描述
线程分离之后是不可以join的,join会返回报错码,线程分离是一种属性状态,如果主线程join等待某个线程,会查看这个线程的状态是否是分离的。如果是分离状态就会直接报错。如果先join线程,再分离线程,会检测不到。

例如这样就会报错

void *run_thread(void *args){//pthread_detach(pthread_t pthread_self()); //可以自己分离自己int cnt = 5;while(cnt){cout<<"我是线程:"<<cnt--<<endl;sleep(1);}
}
int main(){pthread_t t1;pthread_create(&t1,nullptr,run_thread,nullptr);pthread_detach(t1);//可以再主线程里面分离int n = pthread_join(t1,nullptr);if(n!=0)//如果n!=0表示阻塞等待失败,打印返回的错误码{cout<<"error:"<<n<<":"<<strerror(n)<<endl;}return 0;}

在这里插入图片描述

线程间的私有和共享数据

线程共享进程数据,但是也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器
  • errno
  • 信号屏蔽字
  • 调度优先级

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

理解线程库和线程id

我们学习的Linux里面其实是没有真正意义上的线程的,Linux用进程模拟的线程。所谓的线程就是一个轻量级的进程,所以Linux提供了对轻量级进程操作的接口,而我们用的线程库就是对这些接口进行了封装。从用户的角度是对线程进行操作,但是从OS的角度是对轻量级进程的操作。

这个线程库是一个用户级的动态库,所以进程想要用这个库,必须先加载到内存,然后映射到进程地址空间的共享区中。

这个线程库里面可能会创建很多个线程,所以需要对线程进行管理,所以先描述,再组织。每个线程都有一个结构体来对这些线程进行统一管理。
在这里插入图片描述

线程库中有很多个线程,每个线程都是向数组一样排列,每个线程都有一个起始地址,这个起始地址就是线程id,把这个线程id传给其他线程,就可以获取该线程的属性信息。

每个线程都拥有自己的栈结构,主线程用的是地址空间的栈。

深刻理解Linux多线程(重点)

当一个进程创建子进程时,需要创建PCB、进程地址空间、页表等。是非常独立的。一个进程内可以有多个线程,那么线程是怎么创建的呢?

  • 给一个进程创建线程时,只会创建一个PCB,这个PCB还是会指向这个进程,进程地址空间内部有代码区,每个线程指向代码区中不同的代码区域,一个线程对应一个函数代码,这样一个线程就是一个执行流。这就是为什么线程是进程内部的一个执行流。
  • 线程执行粒度更细因为可以执行进程中不同的代码,控制粒度更细。
  • 调度成本更低因为PCB中存放进程地址空间的地址,当CPU切换PCB时,发现不用切换加载进程地址空间以及页表等一系列操作。但是最重要还是不用切换cache(cache是一个集成在CPU里面的硬件,也叫做高速缓存器,当我们访问内存中的代码的时候,会预先加载一部分代码到cache中,减少IO,提升效率,这个也叫做局部性原理。CPU切换的线程如果是同一个进程,cache不用切换数据,使得切换成本更低)。
  • 之前谈到进程是CPU的基本调度单位,因为之前谈论的进程都是一个执行流,一个进程只有一个PCB,而多线程这里,一个进程有多个PCB,也就是多个执行流,对于CPU来说其实调度切换的是进程还是线程,CPU并不知道,也并不重要,CPU只需要可以通过PCB访问进程地址空间,通过页表映射到内存就可以了。
    但是并不是所有的操作系统都是这样设计多线程的,这种是Linux下的多线程,而windows的里面线程和进程是不同的,Windows下的线程叫做TCB(线程控制块),而线程是进程的一个执行流,必须遵守执行粒度更细,调度成本更低,所以Windows的设计是比较复杂的。Windows里面是有真线程的,Linux下则是用进程的方案去模拟线程,所以Linux没有真正意义上的线程,都叫做轻量级进程(LWP)。Linux对比Windows复用代码结构,更简单,好维护,效率更高。

一个进程里面有多个执行流,有一个执行流是主执行流。每个进程都有一个pid,一个进程里面有多个执行流,每个执行流的pid当然都是一样的,但是每个执行流都有一个LWP是不一样的,CPU根据LWP来进行基本的调度。切换执行流,如果pid不变证明还是一个进程,不需要切换地址空间、cache等操作,如果pid变了,证明不是同一个进程了。

代码证明多线程有多个LWP,有同一个pid

void *run_thread1(void *args){while(true){cout<<"我是执行流1:"<<*((int*)args)<<endl;sleep(1);}
}
void *run_thread2(void *args){while(true){cout<<"我是执行流2:"<<*((int*)args)<<endl;sleep(1);}
}
void *run_thread3(void *args){while(true){cout<<"我是执行流3:"<<*((int*)args)<<endl;sleep(1);}
}
int main(){pthread_t t1,t2,t3;int th1=1;pthread_create(&t1,nullptr,run_thread1,&th1);int th2=2;pthread_create(&t2,nullptr,run_thread2,&th2);int th3=3;pthread_create(&t3,nullptr,run_thread3,&th3);while(true){cout<<"我是主执行流"<<endl;sleep(1);}return 0;
}

执行结果
在这里插入图片描述
这段代码使用了一下多线程,证明一个线程有一个LWP。主执行流的LWP和进程的pid是相同的,所以之前学习进程说的CPU根据pid调度进程也是对的。

这就是Linux下的多线程,后续会更新互斥和同步的文章,多多支持。

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

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

相关文章

【selenium】

selenium是一个Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的。Selenium可以直接调用浏览器&#xff0c;它支持所有主流的浏览器。其本质是通过驱动浏览器&#xff0c;完成模拟浏览器操作&#xff0c;比如挑战&#xff0c;输入&#xff0c;点击等。 下载与打…

算法刷题:快乐数

快乐数 .习题链接题目题目解析初始值算法原理我的答案 . 习题链接 快乐数 题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1…

【C语言】解析刘谦春晚魔术《守岁共此时》

今年的春晚上刘谦表演了魔术《守岁共此时》&#xff0c;台上台下积极互动&#xff08;尤其是小尼&#xff09;&#xff0c;十分的有趣。刘谦老师的魔术不仅仅是他的高超手法&#xff0c;还有这背后的严谨逻辑&#xff0c;下面我们来用C语言来解析魔术吧。 源代码 #define _CRT…

单页404源码

<!doctype html> <html> <head> <meta charset"utf-8"> <title>简约 404错误页</title><link rel"shortcut icon" href"./favicon.png"><style> import url("https://fonts.googleapis.co…

SVD奇异值分解

一、奇异值 奇异值&#xff08;Singular Values&#xff09;是线性代数中矩阵的重要性质之一&#xff0c;与奇异值分解&#xff08;SVD&#xff09;密切相关。让我们来更详细地了解一下奇异值的概念&#xff1a; 定义&#xff1a; 对于一个矩阵 ( A )&#xff0c;它的奇异值是…

夜天之书 #95 GreptimeDB 社群观察报告

GreptimeDB 是格睿科技&#xff08;Greptime&#xff09;公司研发的一款开源时序数据库&#xff0c;其源代码[1]在 GitHub 平台公开发布。 https://github.com/GreptimeTeam/greptimedb 我从 2022 年开始知道有 GreptimeDB 这个项目。2023 年&#xff0c;我注意到他们的 Commun…

Blazor OIDC 单点登录授权实例5 - 独立SSR App (net8 webapp ) 端授权

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor OIDC 单点登录授权实例2-登录信息组件wasmBlazor OIDC 单点登录授权实例3-服务端管理组件Blazor OIDC 单点登录授权实例4 …

在线问诊系统设计与实现的经验总结与整理

随着互联网技术的快速发展&#xff0c;在线问诊服务作为一种新兴的医疗服务模式&#xff0c;正逐渐受到人们的关注和使用。本文将介绍在线问诊系统的设计原则和关键组件&#xff0c;以及如何实现一个安全、高效和可扩展的在线医疗服务平台。 内容&#xff1a; 1. 引言 - 在…

团队配置管理规范浅见

在一段时间的工作过程中配置管理工作确实对我们的生产活动产生了巨大的工作量&#xff0c;现在就这个工作来进行梳理一下。 本文主要分为两部分&#xff1a; 1、借用软件系统分析师的配置管理部分内容来介绍配置管理的工作&#xff08;原谅时间精力有限&#xff0c;原文基本已…

Qt窗口坐标体系

通过以上代码可以看出Qt的坐标体系。 以左上角为原点&#xff08;0,0&#xff09;&#xff0c;以向右的方向为x轴的正方向&#xff0c;以向下方向为y轴的正方向。 对于嵌套窗口&#xff0c;其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。

day 20(补2.5)

fread 函数&#xff1a; 今日练习 C语言面试题5道~ 1. static 有什么用途&#xff1f;&#xff08;请至少说明两种&#xff09; 1) 限制变量的作用域 2) 设置变量的存储域 2. 引用与指针有什么区别&#xff1f; 1) 引用必须被初始化&#xff0c;指针不必。 2) 引用初始…

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统(有论文)

【Java程序设计】【C00266】基于Springboot的超市进存销管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的超市进销存系统 本系统分为登录注册模块、管理员功能模块以及员工功能模块。 登录注册模块&#…

《Linux 简易速速上手小册》第8章: 安全性与加固(2024 最新版)

文章目录 8.1 防火墙与安全策略8.1.1 重点基础知识8.1.2 重点案例&#xff1a;配置 iptables 以保护 Web 服务器8.1.3 拓展案例 1&#xff1a;使用 firewalld 配置动态防御区域8.1.4 拓展案例 2&#xff1a;配置 ufw 以简化管理 8.2 SSH 安全最佳实践8.2.1 重点基础知识8.2.2 重…

JAVA设计模式之命令模式详解

命令模式 1 命令模式介绍 命令模式(command pattern)的定义: 命令模式将请求&#xff08;命令&#xff09;封装为一个对象&#xff0c;这样可以使用不同的请求参数化其他对象&#xff08;将不同请求依赖注入到其他对象并且能够支持请求&#xff08;命令&#xff09;的排队执行…

STM32 寄存器操作 GPIO 与中断

一、如何使用stm32寄存器点灯&#xff1f; 1.1 寄存器映射表 寄存器本质就是一个开关&#xff0c;当我们把芯片寄存器配置指定的状态时即可使用芯片的硬件能力。 寄存器映射表则是开关的地址说明。对于我们希望点亮 GPIO_B 的一个灯来说&#xff0c;需要关注以下的两个寄存器…

【C语言期末项目-通讯录】-升级可动态申请内存版(手把手详细过程,期末评分A+的项目,答辩辅助神博文,建议三连点赞收藏)

目录 ​编辑 前言&#xff1a; 1.项目功能需求分析 2.文件框架说明 3.程序主框架实现 4.创建联系人结构体类型和通讯录结构体类型 4.1创建通讯录 5.程序功能实现--封装功能函数实现不同功能 5.1通讯录初始化 5.2增加联系人 5.3显示所有联系人的信息 5.4删除指定…

【Java EE初阶十二】网络编程TCP/IP协议(二)

1. 关于TCP 1.1 TCP 的socket api tcp的socket api和U大片的socket api差异很大&#xff0c;但是和前面所讲的文件操作很密切的联系 下面主要讲解两个关键的类&#xff1a; 1、ServerSocket&#xff1a;给服务器使用的类&#xff0c;使用这个类来绑定端口号 2、Socket&#xf…

【图论】【树形dp】【深度优先搜索】2538. 最大价值和与最小价值和的差值

作者推荐 【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目 本文涉及知识点 深度优先搜索 LeetCode2538. 最大价值和与最小价值和的差值 给你一个 n 个节点的无向无根图&#xff0c;节点编号为 0 到 n - 1 。给你一个整数 n 和一个长度为 n - 1 的二维整数…

使用LoRA和QLoRA微调LLMs:数百次实验的见解

前言 翻译文章《Finetuning LLMs with LoRA and QLoRA: Insights from Hundreds of Experiments》原文地址因译者水平有限&#xff0c;翻译过程中有错误请在评论区指出 提要 LoRA是用于训练自定义LLM的最广泛使用、参数效率最高的微调技术之一。从使用QLoRA节省内存到选择最…

iTop-4412 裸机程序(十九)- 按键中断

目录 0.源码1.异常向量表1.1 原理1.2 异常种类1.3 ARMv7 规定的异常向量表 2. 中断2.1 iTop-4412 中使用的中断相关寄存器 上篇博文介绍了按键的轮询处理方式&#xff0c;本篇介绍按键的中断方式。 0.源码 GitHub&#xff1a;https://github.com/Kilento/4412NoOS 1.异常向量…