【Linux】 由“进程”过渡到“线程” -- 什么是线程(thread)?

news2024/11/25 13:46:06

  • 知识引入
  • 初识线程
    • 1.什么叫做进程?
    • 2.什么叫做线程?
    • 3.如何看待我们之前学习的进程?
  • 理解线程
  • 创建线程函数调用
    • 1.线程一旦被创建,几乎所有资源都是被线程所共享的
    • 2.与进程之间切换相比,线程的切换
  • 初识线程总结:
    • 线程的优点
    • 线程的缺点
    • 线程异常
    • 线程用途
    • Linux进程VS线程

知识引入

如何看待地址空间和页表:

  1. 地址空间是进程能看到的资源窗口
  2. 页表决定,进程真正拥有资源的情况(页表映射多少才是拥有多少)
  3. 合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有的资源进行分类

虚拟地址如何找到物理地址:
在这里插入图片描述
最后一级页表存放的是页框的起始物理地址然后通过虚拟地址后12位为页内偏移量→物理地址


初识线程

1.什么叫做进程?

在这里插入图片描述
进程组成:PCB + 地址空间 + 页表 + 物理内存映射的一部分

2.什么叫做线程?

线程:进程内的一个执行流

虚拟内存里面决定了进程能够看到的“资源”,我们将我们的代码分成一块一块的区域,我们不再像以前那样创建进程就将原进程的虚拟地址空间、页表再复制一份,而是只创建PCB指向父进程指向的位置:
在这里插入图片描述
我们首先要知道:一个进程所对应的资源是可以通过虚拟地址空间和页表将部分资源划分给不同的PCB中的,因为我们可以通过虚拟地址空间+页表方式对进程进行资源划分,单个“进程”执行力度,一定要比之前的进程要细

思考:
如果OS真的要专门设计“线程”概念,OS要不要管理这个线程呢?
 肯定是需要的,方法还是:先描述,在组织。在windows中为线程设计专门的数据结构表示线程对象TCB,而在Linux中单纯从线程调度角度,线程和进程有很多的地方是重叠的!所以,我们的Linux工程师,不想给Linux"线程"专门设计对应的数据结构!而是直接复用PCB
 用PCB用来表示Linux内部的"线程"。而对于CPU来说,它并不关心你的PCB是表示进程还是线程,它只关心task_struct(PCB)

线程在进程内部运行,线程在进程的地址空间内运行,拥有该进程的一部分资源!

3.如何看待我们之前学习的进程?

在这里插入图片描述
进程:一堆的PCB + 地址空间 + 页表 + 对应的物理地址

之前的进程:承担系统资源的基本实体,只不过内部只有一个执行流,一个进程内部可以有多个执行流

站在CPU的角度看待task_struct:以前:进程—现在:进程内的一个分支
CPU很笨,它不会区分,它只需要拿来就能用就行。

Linux下统称task_struct:轻量级进程

小总结:

  1. Linux内核中有没有真正意义的线程呢?没有。Linux是用进程PCB来模拟线程的,是一种完全属于自己的一套线程方案
  2. 站在CPU的视角,每一个PCB,都可以称之为叫做轻量级进程
  3. Linux线程是CPU调度的基本单位,而进程是承担分配系统资源的基本单位
  4. 进程用来整体申请资源,线程用来伸手向进程要资源。(比如公司、小组、组员关系,小组向公司申请资金,组员向小组长拿钱)
  5. Linux中没有真正意义的线程
  6. 好处是什么?简单,维护成本大大降低–可靠高效!
    我们使用PCB来模拟线程,那么我们曾经给PCB创建的一整套的数据结构与算法都可以复用。而且线程与进程有很多地方是重叠的,这也给我们重新创建线程方法的编码难度大大提高,更为复杂的代码,带来的就是维护成本的增高

OS只认线程,用户(程序员)也只认线程
Linux无法直接提供创建线程的系统调用接口,而只能给我提供创建轻量级进程的接口


理解线程

我们将
 家庭比作进程
 家庭成员比作线程
 (家庭成员)你的爸爸妈妈努力工作是为了生活更美好,你的爷爷奶奶每天锻炼跳广场舞是为了身体健康,你每天努力学习是为了未来有更好的生活,(家庭)所有人的目的都是为了这个家更美好。家庭与家庭成员关系类比也就是进程与线程的关系


创建线程函数调用

在这里插入图片描述
函数原型:(具体可看下一章 线程控制)

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
					void *(*start_routine) (void *), void *arg);

创建线程参考:

#include <iostream>
#include <cstdio>
#include <cassert>
#include <pthread.h>
#include <unistd.h>
using namespace std;
// 新线程
void *thread_routine(void *args)
{
    while (true)
    {
        cout << "我是新线程, 我正在运行!" << endl;
        sleep(1);
    }
}

int main()
{
    // typedef unsigned long int pthread_t;
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, thread_routine, (void *)"thread one");
    assert(0 == n);
    (void)n;
    while (true)
    {
        // 地址 -> ?
        cout << "我是主线程, 我正在运行!" << endl;
        sleep(1);
    }
    return 0;
}

直接运行的结果:
在这里插入图片描述
错误:对“pthread create”的未定义引用
原因:这个函数并不是OS提供给我们的系统调用,Linux没有真正意义上的线程,只有轻量级进程,所以Linux只能提供轻量级进程的接口,无法给我们直接创建线程。而这个函数是第三方库提供给我们的,不是语言提供的也不是OS提供的,这个库是:

Compile and link with -pthread.

当然这个库大概率是在系统之中已经有的,为了使用这个第三方库,我们编译时需要找到这个库 -lpthread

g++ -o mythread mythread.cc -lpthread -std=c++11

在这里插入图片描述
在这里插入图片描述

代码执行结果:
在这里插入图片描述
很明显有两个执行流在一起运行,但是:
在这里插入图片描述
在这里插入图片描述
如何看到这两个执行流信息呢?

ps -aL

在这里插入图片描述
在这里插入图片描述
LWP:light weight process 轻量级进程ID
两个线程的PID都是相同的,主线程PIDLWP相同的那个

CPU调度的时候,是以哪一个id为标识符表示特定一个执行流的呢? LWP
当我们只有一个执行流的时候,PID与LWP是相同的,在这种情况下使用PID标识或LWP标识都可以

在这里插入图片描述

1.线程一旦被创建,几乎所有资源都是被线程所共享的

int g_val = 0;
// 新线程
void *thread_routine(void *args)
{
    const char *name = (const char *)args;
    while (true)
    {
        cout << "我是新线程, 我正在运行!" << " : " << g_val++ << " &g_val : " << &g_val << endl;
        sleep(1);
    }
}
int main()
{
    typedef unsigned long int pthread_t;
    pthread_t tid;
    int n = pthread_create(&tid, nullptr, thread_routine, (void *)"thread one");
    assert(0 == n);
    (void)n;
    while (true)
    {
        cout << "我是主线程, 我正在运行!"<< " : " << g_val << " &g_val : " << &g_val << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

线程也一定要有自己私有的资源,什么资源应该是线程私有的呢?

  1. PCB属性私有
  2. 要有一定私有上下文结构
  3. 每一个线程都要有自己独立的栈结构-

2.与进程之间切换相比,线程的切换

  • 进程:切换页表&&虚拟地址空间&&切换PCB &&上下文切换
  • 线程:切换PCB &&上下文切换
  • 线程切换cache不用太更新,但是进程切换,全部更新
    cache:高速缓冲存储器,比CPU的寄存器慢些,但是比内存块
    软件存在一种属性叫做局部性原理,当前访问的代码和数据那么它相邻的代码和数据也非常容易被访问到。
    在这里插入图片描述
     当多个线程切换时,这些热点数据本来就是被线程所共享的,线程切换时,cache不用被切换。因为这部分数据,线程PCB再怎么切换可能之前缓存的数据还是能用得上,能命中,所以缓存根本不用切换。(热点数据就是经常被访问到的数据)
     如果是进程切换,A进程代码和数据保存一大堆,切换到B进程时,上下文一保存,那么之前cache里的缓存数据都失效,新进程B需要重新加载到cache,切换到旧进程A,又要重新加载,来回切换增加了很多成本与效率。


初识线程总结:

进程 VS 线程
Linux进程是申请资源的基本单位,而线程是进程里面的一个小执行流,是CPU调度的基本单位。下图整个框里面是进程,而线程是绿色的PCB执行流
在这里插入图片描述

线程的优点

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

计算密集型应用(CPU,加密,解密,算法等) VS I/O密集型应用(外设,访问磁盘,显示器,网络)

线程的缺点

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
    在这里插入图片描述
     为什么一个线程出问题会影响其余线程?进程信号,信号是整体发给进程的,它会向PID相同的执行流全部写入出错信号。
     线程出异常本身就是进程出异常,比如小组里面的成员删库跑路,公司问责不仅仅要问责这个成员,还需要问责整个小组。一个线程出异常导致整个进程的资源都被释放掉,那么其余的线程赖以生存的资源已经没了,所以也就只能释放

  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高
    编写与调试一个多线程程序比单线程程序困难得多

线程异常

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

线程用途

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

Linux进程VS线程

进程和线程比较:

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据,比如:
    1.线程ID
    2.一组寄存器
    3.栈
    4.errno
    5.信号屏蔽字
    6.调度优先级

在这里插入图片描述

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

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

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

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

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

相关文章

JWT 的使用

一、简介 JWT将用户的一些信息存储在客户端&#xff0c;访问后台时会带着JWT&#xff0c;服务器要对这个JWT进行检验。 由于signKey是存放在服务器端的&#xff0c;所以比较安全只要JWT被篡改就会立刻发现。 JWT认证的优势 1.简洁&#xff1a;JWT Token数据量小&#xff0c;传…

WebRTC带宽评估 -- Transport-wide Congestion Control

简述&#xff1a;在RTP包中增加transport-wide-cc扩展头&#xff0c;放置传输层面的包序号。视频接收端记录RTP包的接收时间&#xff0c;并通过RTCP Feedback消息反馈到视频发送端&#xff0c;发送端结合缓存的RTP包发送时间&#xff0c;基于丢包和延迟估算当前带宽&#xff0c…

zabbix 企业级监控 (3)Zabbix-server监控mysql及httpd服务

目录 web界面设置 server.zabbix.com 服务器操作 编辑 chk_mysql.sh脚本 查看web效果 web界面设置 1. 2. 3. 4. 5. 6. 7. 8. server.zabbix.com 服务器操作 [rootserver ~]# cd /usr/local/zabbix/etc/ [rootserver etc]# vim zabbix_agentd.conf UnsafeUserParameters1 Us…

Java当中的栈

栈的理解 栈&#xff08;Stack&#xff09;是一种受限的线性数据结构&#xff0c;所谓受限是指栈只暴露栈顶和栈底的操作&#xff0c;其底层是由数组实现的。栈的特性是先进后出。 常用方法 注意上面的peek()方法和pop()方法的区别&#xff01; 实例 import java.util.Stack…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 19 日论文合集)

文章目录 一、分割|语义相关(12篇)1.1 Disentangle then Parse:Night-time Semantic Segmentation with Illumination Disentanglement1.2 OnlineRefer: A Simple Online Baseline for Referring Video Object Segmentation1.3 MarS3D: A Plug-and-Play Motion-Aware Model for…

LeetCode74.Search-A-2d-Matrix<搜索二维矩阵>

题目&#xff1a; 思路&#xff1a; 矩阵&#xff0c;搜索数是否在矩阵内。那就查找他是否在每一行中。如果符合这一行的范围&#xff0c;那就一直找这一列是否存在&#xff0c;如果存在返回true&#xff1b;否则false&#xff1b; 代码是&#xff1a; //codeclass Solution …

Istio 安全管理 加密证书中心

1 tls认证 2 设置ACL 允许哪些客户端可以访问 哪些客户端不能访问 3 istio里面的认证 加密是可以分为三种类型 对称加密&#xff08;加密和解密用的是同一个密钥&#xff09;非对称加密哈希函数 对称加密 A要发送数据传送给B&#xff0c;那么A要使用一个密钥&#xff0c;里面…

MySQL-数据库读写分离(下)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

Verilog 学习之路二——基础学习总结(摘取自菜鸟教程)

目录 1 Verilog 设计方法2. 基础语法2.1 格式2.2 数值表示数值种类表示方法 2.3 数据类型2.4 表达式 3. 编译指令4. 连续赋值5. 过程结构6 过程赋值7 时序控制8 语句块9 循环10 函数例子-数码管译码 1 Verilog 设计方法 Verilog 的设计多采用自上而下的设计方法&#xff08;to…

TypeScript + React 环境搭建

React 安装 vscode 或者 webstrom 代码编辑器TypeScript 开发环境搭建1.1、下载 node.js1.2、安装 node.js1.3、npm 安装 typeScript1.4、创建和编写 ts 文件1.5、编译1.6、运行 js文件 React 环境搭建2.1、homebrow2.2、安装 cnpm&#xff1a;2.3、安装yarn&#xff1a;2.4、安…

[oeasy]python0074[专业选修]字节序_byte_order_struct_pack_大端序_小端序

进制转化 回忆上次内容 上次 总结了 计算字符串值的函数 eval 四种进制的转化函数 binoctinthex 函数名前缀目标字符串所用进制bin0b二进制oct0o八进制hex0x十六进制eval无前缀十进制 数字41 和 字符串"41" 的不同 字符串"41" 两个字符字符存储依据是…

部署ELK+Kafka+Filebeat日志收集分析系统

部署ELKKafkaFilebeat日志收集分析系统 文章目录 部署ELKKafkaFilebeat日志收集分析系统一、ELK 简介1、ELK日志分析系统组成2、Elasticsearch&#xff08;es&#xff09;3、Logstash4、Kibana5、日志处理步骤 二、Elasticsearch介绍1、Elasticsearch核心概念2、开启分片副本的…

AcWing 244. 谜一样的牛—树状数组、二分

题目链接 AcWing 244. 谜一样的牛 题目描述 分析 这道题挺巧妙的&#xff0c;感觉树状数组方面的题就是比较难想&#xff0c;先分析一下样例&#xff0c;样例中每头牛前面比自己低的牛的数量分别为 0 1 2 1 0牛的高度是1~n的排列&#xff0c;如何分析出每头牛的高度呢&…

Linux学习之if判断的使用

if的基本用法 if后边可以直接跟着命令。 echo "good" > ifecho.txt把good输出到当前目录下ifecho.txt文件里边&#xff0c;cat ifecho.txt首先可以看一下这个文件里边的内容。 然后依次输入&#xff1a; if cat ifecho.txt thenecho "right" fi可以看…

Windows系统实现唤醒+合成+命令词智能语音交互

1、之前写过离线能力调用&#xff0c;今天来个终极版&#xff0c;实现智能交互或者结合大模型的智能交互示例&#xff0c;下面进入正题。上B站效果离线唤醒离线合成离线命令词实现智能交互_哔哩哔哩_bilibili 2、到讯飞开放平台下载唤醒合成命令词的离线组合包&#xff0c;找到…

关于正则表达式的简单介绍以及使用

一、介绍 正则表达式通常被用来检索匹配某种模式&#xff08;规律&#xff09;的文本 日常文本检索&#xff0c;如果单纯检索某个数字&#xff0c;字母&#xff0c;或者单词匹配出来的结果较多&#xff0c;而面对目标文件内容较大的时&#xff0c;我们也不可能肉眼对检索出来的…

Kotlin 新版本 1.9.0重要更新预览

释放 Kotlin 新版本 1.9.0 的强大功能 1. Kotlin K2编译器用于多平台 对K2编译器进行了进一步的改进&#xff0c;使其更加稳定。K2编译器针对JVM目标现已进入Beta版本&#xff0c;并且也可以在多平台项目中使用。 您可以通过将K2配置添加到项目的gradle.properties中&#x…

pytest-allure 生成测试报告

目录 前言&#xff1a; pytest 中 yield 和 return 的区别和相同点 共同点 区别 usefixtures 与传 fixture 区别 Pytest 常用的插件 一键安装多个模块 前言&#xff1a; 在软件测试中&#xff0c;生成清晰、易读的测试报告是非常重要的。pytest-allure是一个流行的测试…

centos逻辑分区磁盘扩展

最近碰到服务器磁盘空间不足&#xff0c;需要扩展逻辑分区的需求&#xff0c;特地做下小笔记&#xff0c;方便后续自己回忆。下图是磁盘的相关概念示意图&#xff1a; 1、查看磁盘空间 [rootlocalhost ~]# df -h #查看磁盘空间&#xff0c;根分区的大小是18G&#xff0c;已经用…

微服务——Eureka和Nacos

目录 提供者和消费者 ​编辑 Eureka注册中心——远程调用的问题 Eureka注册中心——原理分析 Eureka注册中心——搭建eureka服务 步骤: Eureka注册中心——服务注册 Eureka注册中心——服务发现 总结 Ribbon负载均衡——原理 流程: Ribbon负载均衡——策略 Ribbon负载均衡—…