Linux多线程之初识多线程

news2025/6/9 0:38:36

(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

点个关注不迷路⌯'▾'⌯

目录

一、线程概念

1.vm_area_struct

2.页帧与页框

3.重新理解页表

4.如何理解线程

​编辑5.重新理解进程

1.在用户和内核视角

2.理解之前写的代码

3.CPU视角

6.所以Linux下有线程吗?

二、pthread.h

1.pthread_create

三、线程如何看待进程内部的资源呢?

四、在调度层面上进程和线程的区别

五、线程的优缺点、异常与用途

1.优点

2.缺点

3.异常

4.用途


一、线程概念

1.vm_area_struct

每一个进程都需要有自己的task_struct和虚拟地址空间然后由页表映射物理内存中的

我们的堆空间看似是一个整体,但是我们每次申请都是一小块一小块的申请的,我们也并没有告诉我们的申请什么时候结束,所以我们还需要有一个叫做vm_area_struct的结构体,用来将我们的堆区开辟的空间串联起来,以便可以细致到找到我们每个申请的区域!我们的OS是可以做到让我们的进程进行资源的细粒度划分的!

2.页帧与页框

首先我们要知道,我们磁盘当中的可执行程序也是文件,本来就是按照地址空间方式进行编译的,并且可执行程序也已经被划分成为了以4KB为单位的一个个小文件!

我们把磁盘当中的,以4KB为单位的内容称之为页帧

同样物理内存也被划分为了以4KB为单位的块,那么如此多的内存块要怎么管理呢?OS是怎么直到已经用了多少的呢?

OS是通过数组的方式进行管理的,我们的每个页也就是内存块,就可以当作数组的下标,所以我们对物理内存的管理就变成了对特定数据结构的管理,我们把物理内存对应的4KB大小称之为页框

所以一般我们IO的基本单位是4KB,所以我们的IO的基本过程就是把页帧装进页框里!

缺页中断:页表当中还包含了一些标志性字段,用来代表我们所使用的代码和数据是否在我们内存当中,如果没在,就在我们的内存的page中,申请对应的大小,再根据页表中所对应的磁盘中的可执行程序的内容,将内容通过文件系统加载到物理内存中,然后将所对应的物理地址,填到页表中!

说白了分为这几步:

  • 当操作系统再通过页表进行寻址时,发现所对应的可执行程序不在内存中
  • 所以第一步,先在物理空间中申请我们对应的page
  • 在磁盘找到我们对应的地址。
  • 把目标可执行程序加载到我们的指定内存地址,并重现填写页表
  • 返回到用户,让用户继续进行访问

这个过程用户是0感知的,用户不知道这件事情的发生

3.重新理解页表

因为我们的虚拟地址是32位的系统,所以分为3个部分

第一部分前10个比特位所对应的是:一级页表,用这些比特位去索引二级页表

第二部分中10个比特位所对应的是:在二级页表中的key值,然后所对应的val值就是所对应的页框的起始地址

第三部分后12个比特所所对应的是:用来定位具体的地址,可以理解为偏移量

4.如何理解线程

父子进程是子进程复制父进程的task_struct、虚拟地址空间、页表来映射到地址空间,那么线程呢?

如果我们通过一定的技术手段,将当前进程的“资源”,以一定的方式划分给不同的task_struct,这里每一个task_struct都可以叫做线程

线程在进程的地址空间内运行,是OS调度的基本单位,CPU不关心这个是进程还是线程,只关心task_struct,这是Linux特有的方案

5.重新理解进程

1.在用户和内核视角

进程在用户视角:内核数据结构(所有task_struct、虚拟地址空间、页表)+该进程所对应的代码和数据

进程在内核视角:承担分配系统资源的基本实体!(在创建我们的进程的时候,OS给进程分配资源,是以整个进程为单位分配的,并不是按照task_struct的个数来要的,而其中的线程不再向OS要资源,而是直接像进程去要资源,所以在OS看来,这就是承担分配系统资源的基本实体)

所以现在我们理解进程应该是,进程==一批内核数据结构(一个task_struct一个虚拟地址空间一组页表)+该进程所对应的代码和数据+然后还承担分配系统资源,分门别类地交给每一个线程

2.理解之前写的代码

之前我们都是以单进程的方式来写的,内部只有一个执行流,而今天的内部有多个执行流。

所以什么是进程呢?

进程就是一大批的执行流(至少得有一个)+虚拟地址空间+页表等内核数据结构+该进程所对应的代码和数据,整体以基本单位的方式向OS申请相对应的资源。

所以在Linux的视角下:task_struct就是进程内部的一个执行流

3.CPU视角

CPU根本不怎么关心,当前是进程还是线程,只认task_struct,调度的时候直接执行所调度的task_struct的代码和数据,至于代码是否是和别人共享的或者访问哪个地址空间,CPU根本不在乎,只要是能执行就可以。

所以CPU调度的是task_struct结构体,而不是某个进程,CPU的基本调度单位是“线程”!

在Linux下,task_struct<=其他OS内的task_struct的!Linux下的进程可以称之为轻量级进程,因为CPU拿到了有可能是单个执行流的进程也有可能是多个执行流中的一个线程!

6.所以Linux下有线程吗?

Linux下没有正真意义下的线程结构!因为没有对线程设计专门的数据据结构,但是Linux使用进程PCB来模拟线程的!

所以Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口,所以,Linux给我们提供了一条完整的线程接口,在用户层实现了一套多线程方案,以库的方式提供给用户进行使用:pthread线程库---原生线程库!所以我们只需要调用提供的接口,就可以创建一个线程了!

二、pthread.h

1.pthread_create

用来创建线程的接口

pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*strat_routine)(void *),void *arg)
  • 参数一:线程id
  • 参数二:线程属性,默认就可以
  • 参数三:返回值和参数为void* 的函数指针,传递的这个进程一部分的入口函数
  • 参数四:传递给函数指针的参数
  • 返回值:成功返回0,失败返回错误码和线程id

在编译的时候注意要引入线程库

g++ -o $@ $^ -lpthread -std=c++11
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;

void *threadRun(void *args)
{

    string name = (char *)args;
    while (1)
    {
        cout << name << " pid: " << getpid() << '\n' << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid[5];
    char name[64];
    for (int i = 0; i < 5; i++)
    {
        snprintf(name, sizeof(name), "%s-%d", "thread",i);
        pthread_create(tid + i, nullptr, threadRun, (void *)name);
        // 缓解传参bug
        sleep(1);
    }
    while (1)
    {
        cout << "我是主线程" << getpid() << endl;
        sleep(3);
    }
    return 0;
}

我们可以发现,他们的pid都一样,所以证明了线程在进程中运行!

同时,我们可以看到只有一个进程,但是却有6个执行流

我们也可以发现对应的LWP就是轻量级执行流的意思,所对应的编号也是不一样的,第一个线程的LWP和PID是一样的,所以它叫做主线程,操作系统调度的是LWP!

所以kill如果直接杀掉主线程,这个进程也就直接回收了,所以这个线程的所有子线程也全部回收了!

三、线程如何看待进程内部的资源呢?

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

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

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

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

在我们的虚拟地址空间中栈结构只有一个,而栈又是每个进程私有的,那该怎么分呢?

这是因为在我们所调用pthread线程库中也要给我们提供一个用户层的栈结构!

四、在调度层面上进程和线程的区别

为什么线程切换的成本更低呢?

这是因为地址空间和页表不需要切换,而切换进程需要切换

CPU中是由一个了L1-L3的cache缓存的,就是CPU在进行寻址的时候把这条指令Load到内存里,这样每Load一次就要去访问物理空间一次,这样会大大拖慢CPU的速度,所以一次性会Load多条指令缓存CPU内部,说白了就是会预读一些代码!

可是如果切换进程,cache就直接失效了,只能来重新缓存!

五、线程的优缺点、异常与用途

1.优点

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

但是并不是我们创建线程越多越好,如果有太多的线程,反而变成了切换的成本在进行计算密集型的时候,会频繁的切换,或者IO密集型也会有相同的问题,注意这里是不要过多!

2.缺点

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

3.异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该 进程内的所有线程也就随即退出

4.用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率
  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)

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

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

相关文章

在Blender中清理由Instant-NGP等几何学习技术生成的网格

使用布尔运算: 创建一个大的立方体或其他简单几何体包裹住全部网格。使用布尔修改器对两个网格进行“差集”运算。这将移除超出包裹体之外的多余网格部分。 手动选择并删除: 进入编辑模式&#xff08;按Tab键&#xff09;。按A键取消选择所有顶点。按B键并拖动以选择您想要删除…

❤ Vue3项目搭建系统篇(二)

❤ Vue3项目搭建系统篇&#xff08;二&#xff09; 1、安装和配置 Element Plus&#xff08;完整导入&#xff09; yarn add element-plus --savemain.ts中引入&#xff1a; // 引入组件 import ElementPlus from element-plus import element-plus/dist/index.css const ap…

MySQL-锁:共享锁(读)、排他锁(写)、表锁、行锁、意向锁、间隙锁,锁升级

MySQL-锁&#xff1a;共享锁&#xff08;读&#xff09;、排他锁&#xff08;写&#xff09;、表锁、行锁、意向锁、间隙锁 共享锁&#xff08;读锁&#xff09;、排他锁表锁行锁意向锁间隙锁锁升级 MySQL数据库中的锁是控制并发访问的重要机制&#xff0c;它们确保数据的一致性…

系统安全保证措施-word

【系统安全保证措施-各支撑材料直接套用】 一、 身份鉴别 二、 访问控制 三、 通信完整性、保密性 四、 抗抵赖 五、 数据完整性 六、 数据保密性 七、 应用安全支撑系统设计 软件全套资料下载进主页。

JAVA后端开发面试基础知识(八)——Spring

Spring 1. 什么是 Spring 框架 Spring是一个轻量级Java开发框架 我们一般说 Spring 框架指的都是 Spring Framework&#xff0c;它是很多模块的集合&#xff0c;使用这些模块可以很方便地协助我们进行开发&#xff0c;比如说 Spring 支持 IoC&#xff08;Inverse of Control:控…

pta团队天题题-阅览室(c++)

天梯图书阅览室请你编写一个简单的图书借阅统计程序。当读者借书时&#xff0c;管理员输入书号并按下S键&#xff0c;程序开始计时&#xff1b;当读者还书时&#xff0c;管理员输入书号并按下E键&#xff0c;程序结束计时。书号为不超过1000的正整数。当管理员将0作为书号输入时…

React Three Fiber快速入门

https://threejs-journey.com/lessons/what-are-react-and-react-three-fiber#学习笔记 1.基础知识 resize 填充模版 构建第一个场景 we didn’t have to create a scenewe didn’t have to create the webglrenderthe scene is being rendered on each framethe default…

bug--xxoobject has no attribute xxx

Python 创建类的实例后却不能调用写的方法&#xff0c;检查了半天原来是缩进的问题&#xff0c;def函数不应该和class并列 只能说这个英文空格太小了&#xff0c;看不出来。。。。

实验二(一):IPV4编址及IPV4路由基础实验

一实验介绍 1.关于本实验 IPv4( Internet Protocol Version 4)是 TCP/IP 协议族中最为核心的协议之一。 它工作在 TCP/IP参考模型的网际互联层&#xff0c;该层与 OSI参考模型的网络层相对应。 网络层提供了无连接数据传输服务&#xff0c;即网络在发送分组时不需要先建立连…

vue3引入高德地图

首先注册高德key https://console.amap.com/dev/key/a vue项目中安转地图包 pnpm i amap/amap-jsapi-loader -S 先说最重要核心&#xff0c;踩雷过 页面中需写入以下代码&#xff0c;现在注册的高德key要求强制写入安全密钥 window._AMapSecurityConfig {securityJsCode…

linux系统---selinux

目录 前言 一、SELinux 的作用及权限管理机制 1.SELinux 的作用 1.1DAC 1.2MAC 1.3DAC 和 MAC 的对比 2.SELinux 基本概念 2.1主体&#xff08;Subject&#xff09; 2.2对象&#xff08;Object&#xff09; 2.3政策和规则&#xff08;Policy & Rule&#xff09; …

蓝桥杯-List集合

目录 List集合实例化 List集合实例化步骤 常用方法 ArrayList方法 1&#xff1a;add(Object element) 2&#xff1a;size() 3&#xff1a;get(int index) 4&#xff1a;isEmpty() 5:contains(Object o) 6&#xff1a;remove(int index) 总结ArrayList list集合的特点…

新闻文章分类项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 新闻文章分类模型比较项目报告 项目介绍 背景 新闻文章自动分类是自然语言处理和文本挖掘领域的一个重要任务。正确分类新闻文章不仅能帮助用…

数据库 — 增删查改

一、操作数据库、表 显示 show databases;创建 create database xxx;使用 use xxx; 删除 drop database xxx;查看表&#xff1b; show tables; 查看表结构 desc 表名; 创建 create table 表名(字段1 类型1&#xff0c;字段2 类型2&#xff0c;.... ); 删除 drop table 表名; 二…

每日一题-单词分析

&#x1f308;个人主页: 会编辑的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” 试题题目 试题代码 #include <stdio.h> #include <stdlib.h>int main(int argc, char *argv[]) {// 请在此输入您的代码int count[26]{0},max0,i;char ch;while((chget…

RabbitMQ - 01 - 快速入门

目录 界面总览 创建队列 选择默认交换机 发布消息 查看消息 通过实现以下目标快速入门 界面总览 RabbitMQ Management 界面总览 通道: 传输消息的通道 路由: 接收和路由(分发)消息 队列: 存储消息 消息队列的流程: 生产者将消息发送给路由,路由分发消息到各个队列存储…

.net6Api后台+uniapp导出Excel

之前的这个是vue3写法&#xff0c;后端是.net6Api.net6Api后台VUE3前端实现上传和下载文件全过程_vue3 下载文件-CSDN博客 在现在看来似乎搞的复杂了&#xff0c;本次记录一下.net6Api后台uniapp导出Excel。 后端和之前的不一样&#xff0c;前端也和之前的不一样&#xff0c;…

UCRTBASED.DLL缺失怎么办?UCRTBASED.DLL文件的解决方法分享

UCRTBASED.DLL 是一个属于Microsoft Universal C Runtime (UCRT) 的动态链接库&#xff08;DLL&#xff09;文件。在Windows操作系统中&#xff0c;这个文件提供了一系列C和C标准库函数的实现&#xff0c;这些函数对于支持基于C或C开发的应用程序至关重要。 UCRT是微软为了统一…

如何打sap NOTE

文章目录 1 Introduction2 Method2.1 search note2.2 download note2.3 upload note 3 Summarry 1 Introduction SAP Notes is a set of instructions to remove known errors from the SAP systems. Using the Note Assistant tool, SAP Notes can be applied to the system.…

软件项目试运行方案

一、 试运行目的 &#xff08;一&#xff09; 系统功能、性能与稳定性考核 &#xff08;二&#xff09; 系统在各种环境和工况条件下的工作稳定性和可靠性 &#xff08;三&#xff09; 检验系统实际应用效果和应用功能的完善 &#xff08;四&#xff09; 健全系统运行管理体制&…