秒懂Linux之线程

news2024/11/19 21:53:20

fe594ea5bf754ddbb223a54d8fb1e7bc.gif 

目录

线程概念

线程理解

地址空间(页表,内存,虚拟地址)

线程的控制

 铺垫

线程创建

​编辑

线程等待

线程异常

线程终止

代码

线程优点

线程缺点

线程特点


 

线程概念

线程是进程内部的一个执行分支,线程是CPU调度的基本单位~

以前我们学过:加载到内存的程序称为进程,并且还修正了进程的概念——内核数据结构+进程代码与数据~

在没有学习线程前我们的正文代码执行都是串行调用的,如果能做到并行调用会提高效率,而这就是我们学习线程的原因~

线程理解

通过上图我们是可以了解到创建一个进程的成本是很高的~

那假设我们把正文代码分为3份,创建进程(执行流)时不去创建页表,地址空间,让新出现的两个PCB共同指向第一个进程的地址空间,三个进程各自执行正文代码的各部分区域,达到并行的目的~

地址空间与地址空间上的虚拟地址,本质上也是一种资源~

如果我们想要设计线程,那就必须要让OS进行管理——先描述,再组织~

可是线程和进程有太多类似的地方了,创建的成本开销也大,所以在Linux下是没有真正的线程的,反而是用我们上面所想的那般以进程模拟线程~

这就是为什么CPU是以线程为调度单位的原因,以前我们都是觉得是以进程为调度单位的,那是因为里面只有一个执行流,而现在学习线程后就可以理解多个执行流被cpu所调度。

所以我们重新认识一下什么是进程(内核角度)——承担分配系统资源的基本实体~

我们也不用去刻意区分被调度的task_struct到底是线程还是进程,统一把其当作执行流~也叫做轻量级进程~

地址空间(页表,内存,虚拟地址)

我们先提出一个问题:OS要不要管理内存呢?——要的~

如上图所示OS把内存以4KB的数据块进行划分,我们的程序文件恰好也是以4KB的数据块划分的,这样内存与程序能达到以数据库相互对应的目的~而我们把这些4KB大小的数据块由称为页框/页帧~

我们把页框进行描述,里面存放各自属性的数据。假设内存大小为4GB,那么就可以划分1048576个4KB大小的页框,然后以数组的形式组织起来~

因此可以得证一件事:OS进行内存管理的基本单位为4KB~

我们熟悉内存管理后再回来深度理解多个执行流是如何进行代码划分的~毕竟总不能我们臆想就划分成功的吧?

首先页表肯定不是如此简单地把物理地址与虚拟地址放在一起就说明映射的,地址空间有4G(2^32)而页表如果要映射地址空间中的每个虚拟地址,那么在一行的映射中是要包含物理地址,虚拟地址,权限等数据的,假设这些大小为10byte,那一个页表岂不是要2^32 *10这么大?这成本也是不合实际的~

所以真正的页表是下面这种~

我们把虚拟地址划分为3个区域,其中前10位表示页目录,一共可以存放1024个数据~

中间10位表示页表项,每个页表都可以存放1024个数据~

欧克,我们再来梳理一遍~前面10位用来寻找在页目录中对应的页表项(即在哪个页表),中间10位用来查找  在对应页表内的哪一个数据,里面存放着程序代码所属代码块的物理地址(页框的物理地址)最后12位是偏移量,通过里面存放的物理地址+该偏移量就能找到在对应数据块内部真正代码的位置(页内偏移)~ 然后在页表添上权限,与cpu一同判定为内核态还是用户态~

而这才是真正的页表映射~

给不同线程分配不同的区域,本质上就是让不同的线程各自看到全部页表的子集~

线程的控制

 铺垫

Linux下只有轻量级进程而没有线程是为了减少开发成本,而为了给用户提供线程是引进了线程库供我们使用。 

线程创建

test_thread:testThread.cc
	g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
	rm -f test_thread
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>

void *newthreadrun(void *args)
{
    while (true)
    {
        std::cout << "I am new thread, pid: " << getpid() << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, newthreadrun, nullptr);

    while (true)
    {
        std::cout << "I am main thread, pid: " << getpid() << std::endl;
        sleep(1);
    }
}

在同一程序下,进行了并行调用~

另外这里我们看到了一个新东西:LWP(轻量级进程),而操作系统在进行调度的时候是以LWP进行的,以前只有一个执行流时那么LWP==PID~

tid用于在库中维护,类似前面信号所学的mid,而LWP是用于内核中标识线程的,类似于key~

下面我们再来介绍一个函数:pthread_self(用来查看当前线程的tid)

//把地址转16进制
string ToHex(pthread_t tid)
{
    char id [64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void *newthreadrun(void *args)
{
    while (true)
    {
        cout << "I am new thread, pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, newthreadrun, nullptr);

    while (true)
    {
        cout << "I am main thread, pid: " << getpid() << "new thread tid: "<<ToHex(tid)<<"main thread tid"<<ToHex(pthread_self())<<endl;
        sleep(1);
    }
}

这里也验证了新线程确实是在主线程这里被创建的~

ps:关于新线程与主线程谁先运行这是不确定的,由调度器决定~

最后我们再来做个小demo:让主线程先退出,查看新线程情况~

//把地址转16进制
string ToHex(pthread_t tid)
{
    char id [64];
    snprintf(id,sizeof(id),"0x%lx",tid);
    return id;
}

void *newthreadrun(void *args)
{
    string threadname = (char*)args;
    int cnt = 5;
    while (cnt)
    {
        cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
        cnt--;
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");

    //主线程提前退出
    cout<<"main thread quit"<<endl;
  
    return 0;
}

由此验证一件事:主线程退出==进程退出==所有线程都要退出 

线程等待

线程退出也是需要”wait“的,否则就会和进程那样发送内存泄漏的情况~

功能:等待线程结束
原型
int pthread_join ( pthread_t thread , void ** value_ptr );
参数
thread : 线程 ID
value_ptr : 它指向一个指针,后者指向线程的返回值
返回值:成功返回 0 ;失败返回错误码
调用该函数的线程将挂起等待,直到idthread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的
终止状态是不同的,总结如下 :
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

 

void *newthreadrun(void *args)
{
    string threadname = (char*)args;
    int cnt = 5;
    while (cnt)
    {
        cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
        cnt--;
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");

    int n = pthread_join(tid,nullptr);
    cout<<"main thread quit, n="<<n<<endl;
    sleep(5);


    
    return 0;
}

回收成功~

 

void *newthreadrun(void *args)
{
    string threadname = (char*)args;
    int cnt = 5;
    while (cnt)
    {
        cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
        cnt--;
    }
    return (void*)123;
}

int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");
    //线程回收
    void* ret = nullptr;
    int n = pthread_join(tid,&ret);
    cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;
    sleep(5);

   
    return 0;
}

回收同时取得线程的返回值~ 

线程异常

线程退出有以下三种结果:

  • 代码跑完,结果正确
  • 代码跑完,结果错误
  • 出现异常

而我们重点放在异常上面~


void *newthreadrun(void *args)
{
    string threadname = (char*)args;
    int cnt = 5;
    while (cnt)
    {
        cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
        cnt--;
        //人为造异常
        int*p = nullptr;
        *p =1;
    }
    return (void*)123;
}

int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");
    //测试异常
    while(true)
    {
        sleep(1);
    }

    //线程回收
    void* ret = nullptr;
    int n = pthread_join(tid,&ret);
    cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;
    sleep(5);

   
    return 0;
}

我们发现当新线程出现异常后,连主线程都没了。

重点:在多线程中任意一个线程出现异常,都会导致整个进程退出!这也意味着线程的健壮性不是很好。并且我们还注意到因为线程异常我们连其返回值都接收不到了,因为异常导致所有线程都退出,是走不到回收函数的,整个进程都崩溃了,所以异常时往往不考虑回收了~

线程终止

如果需要只终止某个线程而不终止整个进程 , 可以有三种方法 :
  • 从线程函数return。这种方法对主线程不适用,main函数return相当于调用exit
  •  线程可以调用pthread_ exit终止自己。
  • 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数  

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr 不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
void *newthreadrun(void *args)
{
    string threadname = (char*)args;
    int cnt = 5;
    while (cnt)
    {
        cout << threadname << " is running: " << cnt << ", pid: " << getpid() << "  ToHex(tid):"<<ToHex(pthread_self())<<endl;
        sleep(1);
        cnt--;
    }
    pthread_exit ((void*)123);
}

int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");

    //线程回收
    void* ret = nullptr;
    int n = pthread_join(tid,&ret);
    cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;
    sleep(5);

   
    return 0;
}

 

pthread_cancel函数
功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread: 线程 ID
返回值:成功返回 0 ;失败返回错误码
int main()
{
    pthread_t tid;
    //将字符串"thread-1"传递给形参args
    pthread_create(&tid, nullptr, newthreadrun, (void*)"thread-1");
    //2s后把新线程终止
    sleep(2);
    pthread_cancel(tid);
    //线程回收
    void* ret = nullptr;
    int n = pthread_join(tid,&ret);
    cout<<"main thread quit, n="<<n<<"  ret = "<<(long long)ret<<endl;
    sleep(5);

   
    return 0;
}

 

线程优点

最重要的是前2点:其中第一点不用我们多说,由于线程共享地址空间的缘故它们可以共享大部分资源~

再来谈谈第二点~

我们的认知是进程切换时需要在CPU中的寄存器重新记录数据~

 

后面有了cache后直接在这里取数据更便捷了,但归根到底进程切换还是得考虑cache的记录,因为每一次进程都是不一样的。不过线程就不需要顾虑太多了,它切换后cache概率上还能用~

线程缺点

线程特点

线程私有

  • 线程的硬件上下文(CPU寄存器的值)
  • 线程的独立栈结构

线程公有

  • 代码和全局数据
  • 进程文件描述符

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

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

相关文章

小程序-全局数据共享

目录 1.什么是全局数据共享 2. 小程序中的全局数据共享方案 MboX 1. 安装 MobX 相关的包 2. 创建 MobX 的 Store 实例 3. 将 Store 中的成员绑定到页面中 4. 在页面上使用 Store 中的成员 5. 将 Store 中的成员绑定到组件中 6. 在组件中使用 Store 中的成员 1.什么是全…

【LeetCode】每日一题 2024_10_2 准时到达的列车最小时速(二分答案)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 题目&#xff1a;准时到达的列车最小时速 代码与解题思路 今天这道题是经典的二分答案&#xff0c;结合这道题来讲就是&#xff0c;二分列车的速度 我最擅长的两个算法&#xff1a;一…

cGANs with Projection Discriminator

基于映射鉴别器的CGAN 模型中&#xff0c;判别器&#xff08;Discriminator&#xff09;不是通过将条件信息简单地与特征向量拼接&#xff08;concatenate&#xff09;来使用条件信息&#xff0c;而是采用一种基于投影的方式&#xff0c;这种方式更加尊重条件信息在底层概率模…

进程通信——内存映射

进程通信——内存映射 什么是内存映射 内存映射是一种将文件内容映射到进程地址空间的技术&#xff0c;使得进程可以直接访问文件内容&#xff0c;而不需要通过系统调用进行读写操作。内存映射可以提高文件访问的效率&#xff0c;并且可以实现进程间的通信。 内存映射的原理…

【HarmonyOS】时间处理Dayjs

背景 在项目中经常会使用要时间的格式转换&#xff0c;比如数据库返回一个Date数据&#xff0c;你需要转成2024-10-2的格式&#xff0c;鸿蒙的原生SDK中是没有办法实现的&#xff0c;因此&#xff0c;在这里介绍第三方封装好并且成熟使用的库Dayjs。 安装 切换到Entry文件夹下…

C++初学者指南-5.标准库(第二部分)–特殊迭代器

C初学者指南-5.标准库(第二部分)–特殊迭代器 文章目录 C初学者指南-5.标准库(第二部分)–特殊迭代器容器操纵器std::insert_iterator\<Container>std::back_insert_iterator\<Container>std::front_insert_iterator\<Container> I/O 流迭代器std::istream_i…

2024大二上js高级+ES6学习9.29(深/浅拷贝,正则表达式,let/const,解构赋值,箭头函数,剩余参数)

9.29.2024 1.浅拷贝和深拷贝 Es6的语法糖&#xff1a;用assign将obj对象浅拷贝给o对象。 把数组写在前面是因为数组也是对象 2.正则表达式 创建和检测正则表达式 正则表达式的使用直接跳过&#xff0c;等要用时现查现用 3.ES6 4.let关键字 块级作用域是指在一个{}l里 变量提…

Python | Leetcode Python题解之第441题排列硬币

题目&#xff1a; 题解&#xff1a; class Solution:def arrangeCoins(self, n: int) -> int:left, right 1, nwhile left < right:mid (left right 1) // 2if mid * (mid 1) < 2 * n:left midelse:right mid - 1return left

四、Java 基础语法

一、Java 的类、对象、方法和实例变量 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。对象&#xff1a;对象是类的一个实例&#xff0c;有状态&#xff08;实例变量&#xff…

MySQL基础练习题49-低质量的问题

目录 题目 准备数据 分析数据 总结 题目 找出 低质量 问题的 ID 集合。如果一个力扣问题的喜欢率&#xff08;喜欢数除以总投票数&#xff09;严格低于 60% &#xff0c;则该问题为低质量问题。 按 problem_id 升序排列返回结果表。 准备数据 Create table If Not Exis…

深度学习基础—卷积神经网络示例

1.卷积神经网络的结构 在之前的博客《深度学习—简单的卷积神经网络》&#xff0c;仅由卷积层构成网络的全部&#xff0c;这还不是标准的网络结构&#xff0c;本文将继续介绍标准的卷积神经网络结构有哪些&#xff1f; 深度学习基础—简单的卷积神经网络https://blog.csdn.net…

STM32-按键控制LED 光敏传感器控制蜂鸣器(江协笔记)

1、按键 2、常见传感器模块 R1 定值电阻 N1 传感器电阻。对于光敏传感器来说&#xff0c;相当于光敏电阻&#xff1b;...... C(滤波电容) 给中间的电压输出进行滤波&#xff0c;用于滤除一些干扰&#xff0c;保证输出电压波形的平滑&#xff08;保持电路稳定&#xff09; …

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

多维度柱状图绘制

图形结果 绘制过程 数据如下 调整柱子宽度 Z轴设置 、 配色表

开源链动2+1模式AI智能名片S2B2C商城小程序源码:流量运营中的价值创造与用户影响

摘要&#xff1a;本文深入探讨在开源链动21模式AI智能名片S2B2C商城小程序源码的背景下&#xff0c;流量的激活、信任建立、圈层沉淀以及裂变等流量运营现象。分析流量运营成本与用户消费意识的关系&#xff0c;强调内容在赋予流量价值以影响用户感知和消费判断方面的重要性。 …

基于yolov8深度学习的120种犬类检测与识别系统python源码+onnx模型+评估指标曲线+精美GUI界面目标检测狗类检测犬类识别系统

【算法介绍】 基于YOLOv8深度学习的120种犬类检测与识别系统是一款功能强大的工具&#xff0c;该系统利用YOLOv8深度学习框架&#xff0c;通过21583张图片的训练&#xff0c;实现了对120种犬类的精准检测与识别。 该系统基于Python与PyQt5开发&#xff0c;具有简洁的UI界面&a…

当AI成为作家,人工智能在写作领域的崛起

AI写作技术的应用正在多个领域展现出其强大的潜力和价值&#xff0c;它不仅极大地提升了内容创作的效率&#xff0c;还为创作者提供了一个全新的创作伙伴。 随着技术的进步&#xff0c;AI写作工具越来越能够理解复杂的语境和用户需求&#xff0c;帮助创作者生成高质量的内容。…

DpCas 镜头场景分割 Scene Segmentation

开源项目 - DpCas 镜头场景分割 Scene Segmentation 开源项目地址&#xff1a;https://gitcode.net/EricLee/dpcas 示例&#xff1a;

写出第一个php程序

一、打开vscode&#xff0c;下载chinese插件、php debug、phpintelephense 二、下载完上方图片插件后&#xff0c;创建一个PHP文件&#xff0c;1.php 三、执行命令&#xff0c;成功输出

pytorch搭建神经网络(手搓方法)

假如我们有一个数据集形状为(348,14)。即有348个记录&#xff0c;每个记录有14个特征值。 我们想要搭建一个如下的神经网络&#xff1a; import torch import numpy as np# 创建数据集: 每个样本有14个特征 x_train np.array([[0.5, -1.2, 0.3, 0.8, 1.0, -0.5, 2.3, 1.2, -0…