【Linux】————多线程(概念及控制)

 9efbcbc3d25747719da38c01b3fa9b4f.gif

                                                      作者主页:     作者主页

                                                      本篇博客专栏:Linux

                                                      创作时间 :2024年11月19日

9efbcbc3d25747719da38c01b3fa9b4f.gif

再谈地址空间:

OS对内存进行管理不是根据字节为单位,以字节为单位效率过低,是以内存块为单位的,一个内存块的大小一般为4kb,系统和磁盘IO的基本单位是4kb--8个扇区

文件在磁盘中存储的时候都是有自己的datablock的,每个datablock的大小都是4kb,所以内存管理时,加载就是把程序的数据块加载到指定的内存块中。

为了方便进行表述,4kb的空间+内容有一个名字,叫页框或页帖,在内核中有一个struct page来管理每一个页框,内存看成是数组,第一个page的起始地址就是数组下标,*4==0 ,第二个page的起始地址是数组下标*4==4。所以每个page都有了下标。 

那我们之前所说的虚拟地址到底是如何转化成物理地址的呢?

虚拟地址的前十个比特位索引页目录,中间十个比特位索引页表,页表指向对应页框的起始地址,虚拟地址的低12位+页框的起始地址就能找到页框内的任意一个字节了。

这种页表也叫二级页表,用来搜索页框。

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

线程概念:

  • 在一个程序里的一个执行路线叫做线程,更正确的定义是:线程是一个进程内部的控制序列
  • 一切进程至少有一个执行线程
  • 线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼里,看到的PCB都要比传统的进程更加轻量化
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程合理的分配给每一个执行流,就形成了线程执行流

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

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

我们以前讲的都是进程内部只有一个执行流的进程,Windows系统里有struct tcb结构体描述线程,Linux系统选择复用struct pcb结构体。所以Linux是用进程模拟的线程。

Linux中CPU不区分task_struct 是进程还是线程,都看做执行流。

CPU看到的执行流<=进程。

Linux中的执行流叫:轻量级进程。

创建进程初识:

这个函数的功能是创建一个新的线程:

参数

  • thread:返回的线程ID
  • attr:设置线程的属性,attr为nullptr为使用默认属性
  • start_routine:这是一个函数指针,线程启动后要执行的函数,返回值类型为void*,参数类型为void*
  • arg:传给线程启动函数的参数

返回值:成功返回0     失败返回错误码

看一下我们的代码:

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

void *routine(void *args)
{
    while (true)
    {
        std::cout << "new thread runing...." << std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void *)"pthread-1");
    while (true)
    {
        sleep(1);
        std::cout << "main thread runing..." << std::endl;
    }

    return 0;
}

这里我们记得不可以直接编译,直接编译会出现说直接创建线程是未定义的行为,所以我们要在Makefile中加上

才可以

这个操作的意思是在编译时引入pthread库

运行之后看到:

运行程序, 因为主次线程里都是死循环打印,结果主次线程都有打印,说明有多执行流,即线程创建成功了。

打印出他们的pid,可以看到主次线程的pid都是一样的,因为这两个线程他们都属于同一个进程内部,所以对应的进程pid是一样的。

如果想查看线程,可以通过指令 ps -aL  。他们的pid都是一样的。LWP就是Light Weight Process,即轻量级进程,就是线程的id。

我们把pid和lwp都相等的执行流叫主线程。

线程的优点

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

线程的缺点

  • 性能损失
  • 健壮性降低
  • 编写多线程的程序需要我们更全方面的考虑,在一个多线程程序里,因事件分配上的细微偏差或者因共享了不该共享的变量而造成影响的可能是很大的,换句话说就是线程之间缺乏保护(多线程程序,其中一个线程出了问题,全部线程都会出问题)
  • 缺乏访问控制

线程异常

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

Linux进程vs线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位但也拥有自己的一部分数据
  1. 线程ID
  2. 一组寄存器(重要)
  3. 栈(重要)(线程运行的时候,会形成各种临时变量,临时变量会被每个线程保存在自己的栈区)
  4. errno
  5. 信号屏蔽字
  6. 调度优先级

进程中的多个线程共享一个地址空间,除此之外,各线程还共享以下的进程资源和环境

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

进程和线程的关系图如下:

Linux线程控制 

POSIX线程库 

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文件<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项  

创建线程

前面已经简单介绍了pthread_create的使用。在创建完成后,主线程会继续向下执行代码,新线程会去执行参数3所指向的函数。此时执行流就一分为二了。

线程等待

功能:等待线程结束

参数:

        thread:线程ID

        retval:它指向一个指针,指向线程的返回值(输出型参数)

参数2的类型是void**,用来接收新线程函数的返回值,因为新线程函数的返回值类型是void*。未来要拿到新线程的返回值void*,放到void* retval中时,这里的参数就得传&retval。

返回值:成功返回0;失败返回错误码

这是线程等待的演示代码,下面是结果:

如上图,为pthread_create和pthread_join的简单使用。pthread_t类型由库提供。主线程和新线程谁先运行,这是不确定的。

我们把tid以字符串的形式打印出来,发现是一个虚拟地址,他跟线程id不一样。 

线程函数传参

线程函数传参,可以传任意类型,一定要记住还可以传类对象的地址。 有了这个,就意味着可以给线程传递多个参数,甚至方法了。

上面的td对象是在主线程的栈上的,新线程访问了主线程栈上的临时变量,我们不推荐这种做法。因为如果main函数有第二个对象,他们在读取时没有影响,但其中一个对象在修改时,另一个也会跟着修改。推荐做法如下图:

我们建议在堆上申请一段空间,未来需要第二个对象时,再重新new一个对象,这样多线程就不会互相干扰了。

 创建多线程

完整代码:

const int num=10;
 
void* threadrun(void* args)
{
    std::string name=static_cast<const char*>(args);
    while(true)
    {
        std::cout<<name<<" is running "<<std::endl;
        sleep(1);
        break;
    }
    return args;
}
 
int main()
{
 
 
    std::vector<pthread_t> tids;
    for(int i=0;i<num;i++)
    {
        //1.线程的id
        pthread_t tid;
        //2.线程的名字
        char* name=new char[128];
        snprintf(name,128,"thread-%d",i+1);
        pthread_create(&tid,nullptr,threadrun,name);
 
        //3.保存所有线程的id信息
        tids.push_back(tid);
        //tids.emplace_back(tid);
    }
 
    for(auto tid:tids)
    {
        void* name=nullptr;
        pthread_join(tid,&name);
        // std::cout<<PrintToHex(tid)<<"quit ..."<<std::endl;
        std::cout<<(const char* )name<<" quit..."<<std::endl;
        delete (const char* )name;
    }
    return 0;
}

 运行结果为什么线程名是乱的?因为即使我们的线程是按顺序创建的,但他们不是按顺序启动的。而且上面的name,属于main函数栈上的空间,即main函数栈空间上的公共区传给了每一个线程,所以线程名会被不断覆盖。所以上面这么写是有问题的,要在堆在开辟空间。

线程终止

运行后,发现主线程没有打印quit语句。因为exit是专门用来终止进程的,不能用来终止线程。任意一个线程调用exit,都可能会导致进程退出。 

除了用return结束线程,pthread_exit是专门用来终止一个线程的,使用如下图:

下面是终止线程的另一种方法: 

主线程调用pthread_cancel取消新线程。取消一个线程的前提是线程得存在。

线程取消一个就join一个。由上图可知,线程被取消后,线程的退出结果是-1。

-1对应pthread库中的一个宏

分离线程

作用:哪个线程调用该接口,就返回他自己的线程id。相当于以前的getpid。

void* threadrun(void* args)
{
    pthread_detach(pthread_self());
    std::string name=static_cast<const char*>(args);
    while(true)
    {
        std::cout<<name<<" is running "<<std::endl;
        sleep(3);
        break;
    }
    pthread_exit(args);//专门终止一个线程
}
 
int main()
{
    std::vector<pthread_t> tids;
    for(int i=0;i<num;i++)
    {
        //1.线程的id
        pthread_t tid;
        //2.线程的名字
        char* name=new char[128];
        snprintf(name,128,"thread-%d",i+1);
        pthread_create(&tid,nullptr,threadrun,name);
 
        tids.emplace_back(tid);
    }
 
 
    for(auto tid:tids)
    {
        void* result=nullptr; //线程被取消,线程的退出结果是-1
        int n=pthread_join(tid,&result);   
        std::cout<<(long long int)result<<" quit... ,n: "<<n<<std::endl;
    }
    return 0;
}

 运行上面代码,程序直接挂掉了。因为新线程已经分离,主线程不会卡在join,而是会继续往后走,主线程结束了,整个进程就结束了,新线程可能还没起来就死亡了。

所以分离线程后,主线程就可以做自己的事了,不用管新线程。

即使新线程分离,只要分离的线程异常了,还是会影响整个进程。

除了可以让新线程自己分离,也可以由主线程进行分离。

C++11使用多线程

C++11里使用多线程,创建时是支持可变参数的。大致用法跟前文讲的差不多。

我们把makefile文件里的 -lpthread 去掉然后编译。

编译后,报错了,链接时报错。所以C++语言在Linux中要编译支持多线程,也要加 -lpthread

 C++11的多线程本质:就是对原生线程库接口的封装。

Linux中,C++11要支持多线程,底层必须封装Linux环境的pthread库,编译的时候都得带。

在Windows下要编译多线程程序不用带-lpthread。 

最后:

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。

2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。

3.成年人的世界,只筛选,不教育。

4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。

5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。

最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯每日真题 - 第17天

题目&#xff1a;&#xff08;最大数字&#xff09; 题目描述&#xff08;13届 C&C B组D题&#xff09; 题目分析&#xff1a; 操作规则&#xff1a; 1号操作&#xff1a;将数字加1&#xff08;如果该数字为9&#xff0c;变为0&#xff09;。 2号操作&#xff1a;将数字…

视频融合×室内定位×数字孪生

随着物联网技术的迅猛发展&#xff0c;室内定位与视频融合技术在各行各业中得到了广泛应用。不仅能够提供精确的位置信息&#xff0c;还能通过实时视频监控实现全方位数据的可视化。 与此同时&#xff0c;数字孪生等技术的兴起为智慧城市、智慧工厂等应用提供了强大支持&#…

SIMCom芯讯通A7680C在线升级:FTP升级成功;http升级腾讯云对象储存的文件失败;http升级私有服务器的文件成功

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

前端访问后端实现跨域

背景&#xff1a;前端在抖音里做了一个插件然后访问我们的后端。显然在抖音访问其他域名肯定会跨域。 解决办法&#xff1a; 1、使用比较简单的jsonp JSONP 优点&#xff1a;JSONP 是通过动态创建 <script> 标签的方式加载外部数据&#xff0c;属于跨域数据请求的一种…

网络安全-web架构-nginx配置

1. nginx访问&#xff1a; 访问的是index.html&#xff0c; 访问ip访问的资源就是在/usr/share/nginx/html中&#xff1b; 当nginx不认识&#xff0c;浏览器认识的话&#xff0c;浏览器会自动渲染。 当nginx认识&#xff0c;浏览器不认识的话&#xff0c;浏览器会把它加载成…

内网穿透(组网)成功率高、部署简单

【背景】 公司有服务器&#xff0c;或者公司的电脑配置比自己家里的笔记本高&#xff0c;如果要配置外网穿透&#xff0c;就太麻烦&#xff0c;而且也不安全&#xff0c;局域网组网就相对来说既简单&#xff0c;又安全好多。 ​【介绍】 节点小宝是拥有一套完整的自主研发 P2…

【设计模式】行为型模式(四):备忘录模式、中介者模式

《设计模式之行为型模式》系列&#xff0c;共包含以下文章&#xff1a; 行为型模式&#xff08;一&#xff09;&#xff1a;模板方法模式、观察者模式行为型模式&#xff08;二&#xff09;&#xff1a;策略模式、命令模式行为型模式&#xff08;三&#xff09;&#xff1a;责…

Java从入门到精通笔记篇(十三)

与流处理 ambda表达式 定义 lambda表达式不能被独立执行&#xff0c;因此必须实现函数式接口&#xff0c;并且会返回一个函数式接口的对象。 可将其语法用下列的方式理解 误区警示 “->”符号是由英文状态下的“-”和“>”组成的&#xff0c;符号之间没有空格。 lambd…

阅读2020-2023年《国外军用无人机装备技术发展综述》笔记_技术趋势

目录 文献基本信息 序言 1 发展概况 2 重点技术发展 2.1 人工智能技术 2.1.1 应用深化 2.1.2 作战效能提升 2.2 航空技术 2.2.1螺旋桨设计创新 2.2.2 发射回收技术进步 2.3 其他相关技术 2.3.1 远程控制技术探 2.3.2 云地控制平台应用 3 装备系统进展 3.1 无人作…

VuePress+Github 部署一个零成本静态站点(博客)

VuePress链接:Home | VuePress (vuejs.org)https://vuepress.vuejs.org/ 一.运行环境准备 需要准备安装VSCode(编辑器)和前端运行环境(nvm,node.js和npm) VSCod安装链接:Visual Studio Code - Code Editing. Redefinedhttps://code.visualstudio.com/前端环境:注意需要先安装…

脚手架vue-cli,webpack模板

先安装node.js&#xff0c;它是服务器端&#xff0c;用于给页面提供服务。前端学习不需要会node.js&#xff0c;只需要学会node.js衍生出来的npm命令即可。 npm 是node.js的一个工具&#xff0c;作用是进行包管理&#xff0c;npm是node.js的包管理器。 接着安装脚手架&#xff…

ODOO学习笔记(12):自定义模块开发

一、Odoo模块结构基础 基本目录结构 Odoo自定义模块通常有一个特定的目录结构。一个典型的模块目录包含以下文件和文件夹&#xff1a; __init__.py&#xff1a;这是一个Python模块初始化文件。它使得该目录被视为一个Python模块。在这个文件中&#xff0c;你可以通过from. impo…

在 Sui 区块链上创建、部署与测试自定义 Move 合约的完整教程

系列文章目录&#x1f60a; Task1&#xff1a;hello_move Task2&#xff1a;move_coin 目录 系列文章目录&#x1f60a;引言一、更新本地代码1、查看当前项目的远程仓库信息。2、将远程仓库的最新代码同步到本地的代码分支 二、创建一个新的 Move 项目三、编写合约代码1、编写…

【数据结构】归并排序 —— 递归及非递归解决归并排序

归并排序 一、归并排序1、归并排序的思想2、归并排序代码实现&#xff08;递归&#xff09;<1> 归并排序的递归区间<2> 归并排序的稳定性<3> 拷贝 3、归并排序代码实现&#xff08;非递归&#xff09;<1> 循环区间溢出问题 二、总结 一、归并排序 1、…

单片机学习笔记 6. 数码管动态显示

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示 目录 0、实现的功能 1、Keil工程 1-1 数码管动态显示 1-2 数组的定义与引用…

go 学习网站,go例子 go demo go学习视频

1. 代码例子&#xff1a; Go by Example 2. b站 视频&#xff1a; 尚硅谷视频&#xff1a; 004_尚硅谷_程序的基本概念_哔哩哔哩_bilibili 3. go技术文档&#xff1a; fmt Go语言中文文档

MySQL(5)【数据类型 —— 字符串类型】

阅读导航 引言一、char&#x1f3af;基本语法&#x1f3af;使用示例 二、varchar&#x1f3af;基本语法&#x1f3af;使用示例 三、char 和 varchar 比较四、日期和时间类型1. 基本概念2. 使用示例 五、enum 和 set&#x1f3af;基本语法 引言 之前我们聊过MySQL中的数值类型&…

# 06_ Python基础到实战一飞冲天(二)-python基础(六)--变量的使用与变量类型

06_ Python基础到实战一飞冲天&#xff08;二&#xff09;-python基础&#xff08;六&#xff09;–变量的使用与变量类型 一、程序执行原理-06-明确程序的作用 1、程序的作用 程序就是 用来处理数据 的&#xff01; 2、各类软件的场景&#xff1a; 新闻软件 提供的 新闻内容…

人工智能之机器学习5-回归算法1【培训机构学习笔记】

培训内容&#xff1a; 模型评估 培训班上课的PPT里很多错误&#xff0c;即使讲了很多年也从没改正过来。 而且很多字母没有给出具体的解释&#xff0c;比如RSS和TSS&#xff0c;对初学者非常不友善。 个人学习&#xff1a; 分类和回归的区别 回归和分类是机器学习和统计学…

实验十三 生态安全评价

1 背景及目的 生态安全是生态系统完整性和健康性的整体反映&#xff0c;完整健康的生态系统具有调节气候净化污染、涵养水源、保持水土、防风固沙、减轻灾害、保护生物多样性等功能。维护生态安全对于人类生产、生活、健康及可持续发展至关重要。随着城市化进程的不断推进&…