多线程的高手——海王(浅谈线程概念)

news2024/11/23 10:05:16

听到大海的声音了吗

让我想想炉石里面能被成为海王的卡牌

我个人感觉

就是拿着三叉戟的甲壳元素

可是这牌被删了我心痛

 背景知识

 还是地址空间那点破事!

OS进行内存管理,不是以字节为单位的,而是以内存块为单位的!

默认是4kb捏(Linux主流操作系统默认大小)

系统和磁盘文件进行IO的基本单位是4KB,也就是八个扇区

计算机中的所有巧合,都是被精心设计过的

内存是存储的空间

那4KB也可以被称为页框或者页帧,OS对内存的管理工作基本单位是4KB

拷贝的时候也是把所有的页框都拷贝过去,这是用空间换取时间的一种做法

struct page
{
    int flag;     //是否被占用,是否是脏页,是否被锁定
    int mode;
    
    ...
}

怎么组织呢?用数组管理捏!

struct page memory[1048576];

用下标就可以知道每一个内存页框的地址

虚拟地址是怎么转换成物理地址的呢?

比如说,前十个,中十个,后十个,大后十个。。。

于是就这样,史、史、史。。。

虚拟地址在操作系统看来把它拆成了

10/10/12

难以评价 

页表也不是只有一张的

前十个的数据范围是2的十次方

第一个页表有1024个元素,会以虚拟地址的前10个bit为作为索引来查第一张表

这张页表被称为页目录

页目录里面的东西还要指向页表(页表本质是搜索页框),页目录中存放的是二级页表的地址,而页表中存放的是指向页框的起始地址,而这个+12[0,4095]就叫做页内偏移!

有一组寄存器:

CR寄存器,其中的CR2寄存器是页故障寄存器,保存最后一次出现页故障的全32位线性地址

 假设正文代码中有二十个函数,将他们拆分一下

在技术上有没可行性呢?

你看我给你分析,函数有地址,每一行代码都有地址,而且同一个函数我们认为其地址是连续的

函数是什么呢?

是连续的代码地址构成代码块,一个函数对应一批连续的虚拟地址

虚拟地址的本质是一种资源

线程的概念/Linux中线程的实现

线程是在进程内部运行,是CPU调度的基本单位

还是那几个固定成员:task_struct、地址空间、页表、物理内存

但是我们创建进程的时候如果不再创建物理空间和页表

就是再创建个task_struct出来,但是不额外创建地址空间和页表,而是去指向之前已经创建好的地址空间和页表,而正文代码被分成不同的部分交给这些伙伴去执行,这些伙伴共享这个进程的地址空间,而这个伙伴就叫线程!

tips:

在一个程序里的一个执行路线就叫做线程(thread)

线程是“一个进程内部的控制序列”

一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行

在Linux系统中,在CPU眼中,PCB都要比传统的进程更加轻量化,透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

 

进程=内核数据结构+进程代码和数据

内核上的观点认为励志轩可以玩炉石传说

进程是承担分配系统资源的基本实体

 

EPI人魅力时刻 

哦?还有高手?

拿故事来举例的话就是家庭是进程,而家中的每个人就是线程,上学的上学,上班的上班,养老的养老,各司其职,但最终目的都是好好过日子

之前的进程是内部有一个执行流的进程

而现在的多线程的进程是内部有多个执行流的进程

 

如果OS要单独设计线程,那么这个线程是否要被新建?暂停?销毁?调度?

线程要不要和进程产生关联捏?

如果设计出了线程,那是要设计出线程的id、优先级、上下文、链接属性

进程有进程控制块,而线程也有线程控制块,而线程的PCB也要和进程的关联起来

Windows提供了真实的线程控制块

那能否复用PCB,用PCB统一表示执行流的概念呢?

这样就无需为线程单独设计数据结构和调度算法了,而Windows是存在设计方案PCB的

Windows既要维护进程也要维护线程,Linux是进行了代码的复用,在这样的情况下,Linux的设计是略优于Windows的(Linux用进程模拟线程)

那CPU要不要区分task_struct是进程还是线程?

不需要,和它无瓜。。。不做区分,都是执行流

CPU看到的执行流<=进程,都叫轻量级进程

线程:在进程内部运行,是CPU调度的基本单位

进程是承担分配系统资源的基本实体

在Linux上创建线程要用这样的接口:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

第三个参数是函数指针,是要让新线程执行的方法,第一个参数保存创建完的线程的id

那我们写一段创建新线程的代码吧:

#include <iostream>
#include <unistd.h>

// 新线程
void *threadStart(void *args)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread running..." << std::endl;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadStart, (void *)"thread-new");

    // 主线程
    while (true)
    {
        sleep(1);
        std::cout << "main thread running..." << std::endl;
    }
    return 0;
}

而线程在编译的时候需要带上线程库的选项 

testthread:testthread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f testthread

 为了保证我们看到的是多线程,就让他们把pid都告诉我们一下

#include <iostream>
#include <unistd.h>

// 新线程
void *threadStart(void *args)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread running..." << ",pid: " << getpid() << std::endl;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadStart, (void *)"thread-new");

    // 主线程
    while (true)
    {
        sleep(1);
        std::cout << "main thread running..." << ",pid: " << getpid() << std::endl;
    }
    return 0;
}

 

可以看到虽然是两个执行流,但是只有一个进程

 怎样查看线程捏?

ps -aL

 

那LWP是什么?

是轻量级进程(Light Weight Process)

那OS调度的时候看的是pid还是LWP?

 肯定是LWP呀

PID和LWP一样的是主线程

有一些问题:

已经有多进程了,为什么还要有多线程?

进程的创建成本非常高!

地址空间、页表、物理内存...

而线程创建的成本比较低(启动),线程的调度成本也低(运行)

那删除一个线程呢?

也比较方便,进程具有独立性,但是线程间会影响,一个线程挂了其他线程会受到影响

为什么说线程的调度成本更低?

首先地址空间是共享的,是同一张页表

CPU在调度进程的时候

不同系统对于进程和线程的实现是不一样的

CPU内存在着cache,我们的代码会被预先加载到cache当中,那是热数据,cache是集成在CPU中的硬件

炉石玩家传统美德

从哪可以查到呢?

其实我们 lscpu 就可以查看CPU的属性

如果我们是进程间切换

每个进程作用不一样,要再进行切换,所以cache里的数据就都要重新换

但是线程在切换的时候就不用,因为cache上的数据是可能被用上的

那如果进程执行的代码就是一款OS呢?(不是这什么新赛道)

这就是虚拟机啊(一个进程挂掉也不会影响另一个,不是哥们?)

透过进程的虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流,而线程也不是越多越好,是合适的才是最好的

如果是两核的CPU那就创建两个线程比较好

线程优点

总结线程优点:

☃ 创建一个新线程的代价要比创建一个新进程小得多

☃ 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

☃ 线程占用的资源要比进程少很多

☃ 能充分利用多处理器的可并行数量

☃ 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

☃ 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

☃ I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程缺点

性能损失

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

健壮性降低

        编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的

缺乏访问控制

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

编程难度提高

        编写与调试一个多线程程序比单线程程序困难得多(这难道不是我的缺点吗)

 我们可以验证一下线程健壮性低这个猫饼:

#include <iostream>
#include <unistd.h>
#include <ctime>

// 新线程
void *threadStart(void *args)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread running..." << ",pid: " << getpid() << std::endl;

        int x = rand()%5;
        if(x == 0)
        {
            int *p = nullptr;
            *p = 100;       //野指针
        }
    }
}

int main()
{
    srand(time(nullptr));
   
    pthread_t tid1;
    pthread_create(&tid1, nullptr, threadStart, (void *)"thread-new");
    
    pthread_t tid2;
    pthread_create(&tid2, nullptr, threadStart, (void *)"thread-new");
    
    pthread_t tid3;
    pthread_create(&tid3, nullptr, threadStart, (void *)"thread-new");

    // 主线程
    while (true)
    {
        sleep(1);
        std::cout << "main thread running..." << ",pid: " << getpid() << std::endl;
    }
    return 0;
}

像搓进程脚本一样,我们可以这样搓出来线程的脚本:

while :; do ps -aL |head -1 &&ps -aL |grep 'testthread'; sleep 1; done

可以看到全挂了: 

我们线程是这样的捏: 

 关于缺乏访问控制,也可以用这段代码验证一下:

#include <iostream>
#include <unistd.h>
#include <ctime>

int gval = 100;

// 新线程
void *threadStart(void *args)
{
    while (true)
    {
        sleep(1);
        std::cout << "new thread running..." << ",pid: " << getpid() << " gval: " << gval << " &gval: " << &gval << std::endl;

        // int x = rand()%50;
        // if(x == 0)
        // {
        //     int *p = nullptr;
        //     *p = 100;       //野指针
        // }
    }
}

int main()
{
    srand(time(nullptr));

    pthread_t tid1;
    pthread_create(&tid1, nullptr, threadStart, (void *)"thread-new");

    pthread_t tid2;
    pthread_create(&tid2, nullptr, threadStart, (void *)"thread-new");

    pthread_t tid3;
    pthread_create(&tid3, nullptr, threadStart, (void *)"thread-new");

    // 主线程
    while (true)
    {
        sleep(1);
        std::cout << "main thread running..." << ",pid: " << getpid() << std::endl;
        std::cout << "new thread running..." << ",pid: " << getpid() << " gval: " << gval << " &gval: " << &gval << std::endl;
        gval++;             //修改
    }
    return 0;
}

大部分地址空间的资源,多线程都是共享的(要是有的线程干扰了别的线程就不太好了)

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

线程用途

进程是资源分配的基本单位

线程是调度的基本单位

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

🍇 线程中私有的部分包括:一组寄存器(硬件上下文数据---线性可以动态运行)、栈(线程在运行的时候,会形成各种临时变量,临时变量会被每个线程保存在自己的栈区)、线程ID、errno、信号屏蔽字、调度优先级

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

文件描述符表

每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)

当前工作目录

用户id和组id

写多线程和库有什么关系呢?

为什么多线程会涉及到库这样的概念呢?

Linux系统中没有线程,只有轻量级进程(但这不就是线程吗)

Linux系统没有单独设计PCB,系统只会给上层用户提供轻量级进程的接口

pthread库是Linux自带的原生线程库,会对轻量级进程的接口进行封装,把这些接口按照线程接口的方式交给用户

所以Linux的线程是用户级线程

而Windows中的线程是内核级线程

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

 

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

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

相关文章

Halo 开发者指南——项目运行、构建

准备工作 环境要求 OpenJDK 17 LTSNode.js 20 LTSpnpm 9IntelliJ IDEAGitDocker&#xff08;可选&#xff09; 名词解释 工作目录 指 Halo 所依赖的工作目录&#xff0c;在 Halo 运行的时候会在系统当前用户目录下产生一个 halo-next 的文件夹&#xff0c;绝对路径为 ~/ha…

学习图解算法 使用C语言

图解算法 使用C语言 也就是通过C语言实现各种算法 链接&#xff1a;百度云盘 提取码&#xff1a;1001

【CMake】使用CMake在Visual Stdudio构建一个最简单的项目

一、准备工作 首先&#xff0c;确保在 V i s u a l S t u d i o Visual\ Studio Visual Studio上安装了 C C C桌面开发&#xff0c;如果没有安装&#xff0c;打开 V i s u a l S t u d i o I n s t a l l e r Visual\ Studio\ Installer Visual Studio Installer就可以修改…

【JAVA干货店】带你玩转数组与递归

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 递归利用递归求斐波那契数列数组入门 递归 自己调用自己 StackOverflowError:栈溢出错误,出现的原…

滑动窗口(3)_最大连续1的数组个数III

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 滑动窗口(3)_最大连续1的数组个数III 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; …

CTFHub技能树-信息泄露-HG泄漏

目录 漏洞产生原因 解题过程 当开发人员使用 Mercurial 进行版本控制&#xff0c;对站点自动部署。如果配置不当,可能会将.hg 文件夹直接部署到线上环境。这就引起了 hg 泄露漏洞。 漏洞产生原因 Mercurial(hg)是一种分布式版本控制系统&#xff0c;它与Git类似也可以用于管…

【Java】线程状态:线程生命周期的六个阶段

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持&#xff01; 在Java中&#xff0c;线程可以处于多种状态&#xff0c;这些状态描述了线程的生命周期。了解这些状态及其转换条件对于编写高效且无错误的多线程应用程序至关重要。本文将总结Java线程的几种状态&am…

半导体制造技术中的沉积和驱入(Deposition and drive-in)过程

来源&#xff1a;半导体制造技术导论——萧宏 沉积和驱入过程 图5.34 硼掺杂工艺高温扩散炉系统示意图 图5.35 扩散掺杂工艺流程 图5.36 扩散工艺在超浅结深&#xff08;USJ&#xff09;上的应用

C++设计模式——Prototype Pattern原型模式

一&#xff0c;原型模式的定义 原型模式是一种创建型设计模式&#xff0c;它允许通过克隆已有对象来创建新对象&#xff0c;从而无需调用显式的实例化过程。 原型模式的设计&#xff0c;使得它可以创建一个与原型对象相同或类似的新对象&#xff0c;同时又可以减少对象实例化…

项目实战应用Redis分布式锁

Redis分布式锁 一、前言二、Redis分布式锁过期处理三、Redis分布式实现3.1 基于Jedis 的API实现分布式锁3.1.1 基础命令3.1.2 基于Jedis API的分布式锁3.1.3 基于Lua脚本实现分布式锁 四、Redisson的使用五、Redision锁 核心源码分析六、总结 一、前言 对于项目中使用Redis分布…

jdk相关介绍

JDK&#xff0c;全称Java Development Kit&#xff0c;是Java语言开发的基础工具包。它包含了Java运行时环境&#xff08;JRE&#xff09;以及用于开发Java应用程序的各种工具和库。JDK为Java程序员提供了编译、调试和运行Java应用程序所需的全部环境。 JDK的主要组成部分包括&…

OpenCV_图像像素读写操作

本文详细介绍了如何在C项目中使用OpenCV进行图像像素的读写操作&#xff0c;包括使用头文件声明Pixel类&#xff0c;通过遍历和指针方式处理灰度图和彩色图&#xff0c;以及在主函数中调用这些操作。 数组遍历的方式进行图像像素读写 void QuickDemo::pixelVisit_Demo(Mat&am…

【最新华为OD机试E卷-支持在线评测】增强的strstr(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

替换 Oracle ,江河信息用 TDengine 解决高基数查询写入问题

在数字经济快速发展的背景下&#xff0c;智慧水利作为重要的基础设施之一&#xff0c;正逐步成为提升水资源管理效率、优化生态环境的重要力量。江西省水投江河信息技术有限公司&#xff08;以下简称“江河信息”&#xff09;作为高新技术国有企业&#xff0c;坚定致力于打造数…

监控binlog日志监控表变化进行消息通知

前言 由于我们做项目的时候有项目任务管理工具&#xff0c;目前用的最多的是禅道&#xff0c;而我们用的是redmine&#xff0c;而redmine是使用ruby写的&#xff0c;刚好我们这边没有会用ruby的人&#xff0c;所以就有了这样一个小工具&#xff0c;用于监控binlog日志通过钉钉…

机器人相关知识的本身和价值

简要将人类简史分为 农业工业信息智能 四个时代。 在信息时代&#xff0c;知识本身就可以等同于价值。 常识看&#xff0c;学历可以变现&#xff0c;高品质文凭能极大概率获得工资远远高于平均值的工作机会。 在智能时代&#xff0c;知识本身毫无价值&#xff0c;知识的应…

UML 类图(提供 Java 实现)

文章目录 UML 类图概述及作用类图表示法类&#xff08;接口&#xff09;的表示类与类之间关系的表示关联关系&#xff08;Association&#xff09;单向关联&#xff08;Unidirectional Association&#xff09;双向关联&#xff08;Bidirectional Association&#xff09;自关联…

大学生看过来,必备4款写论文AI写作网站先稿后付

在当今学术研究和写作领域&#xff0c;AI论文写作工具的出现极大地提高了写作效率和质量。这些工具不仅能够帮助研究人员快速生成论文草稿&#xff0c;还能进行内容优化、查重和排版等操作。其中&#xff0c;千笔-aipasspaper是一个备受推荐的平台&#xff0c;它结合了先稿后付…

7.2 溪降技术:下攀

目录 7.2 下攀概述视频课程观看电子书&#xff1a;下攀 一级风险评估装备个人动作技术:面壁下攀烟囱下攀 协助队友总结 7.2 下攀 概述 下攀可能是峡谷探险中最被低估的技能。峡谷中经常存在可以下攀的小落差&#xff0c;这种方式比设置绳索快得多。一组熟练的下攀者能迅速完成…

JavaScript高级——闭包的理解

1、如何产生闭包&#xff1f; —— 当一个嵌套的内部&#xff08;子&#xff09;函数引用了嵌套的外部&#xff08;父&#xff09;函数的变量&#xff08;函数&#xff09;时&#xff0c;就产生了闭包。 2、闭包到底是什么&#xff1f; —— 使用 chrome 查看 —— 理解一&a…