Linux下多线程的相关概念

🤖个人主页:晚风相伴-CSDN博客

💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧

🙏如果内容有误或者有写的不好的地方的话,还望指出,谢谢!!!

让我们共同进步

下一篇《线程的互斥和同步》敬请期待

目录

🔥Linux线程的概念

👍理解Linux下的线程

线程的异常 

🔥进程VS线程 

线程控制 

🔥线程相关函数 

线程创建

线程等待

线程终止

获取线程ID 

线程分离


🔥Linux线程的概念

比较官方的答案线程是进程中的一条执行流,它是被系统独立调度和分配的基本单位。在一个进程内的多个线程可以共享该进程所拥有的全部资源,并且这些线程可以并发执行。简而言之,线程是程序中一个单一的顺序执行流,允许在单个程序中同时运行多个线程以完成不同的工作,这种技术被称为多线程。

举个例子:一个工厂里面有很多的车间,每个车间里都有许多的工人,每个工人各司其职,完成领导交代的任务。因此这里的工人就可以想象成是线程,每个车间就可以想象成是一个进程,而工厂就可以想象成是一台计算机。每一台计算机内可以拥有许多的进程,而在进程内部又可以有许多条执行流,可以分别处理不同的任务。

👍理解Linux下的线程

我们知道进程具有独立性,每一个进程都有自己独立的PCB(task_struct进程控制块)、地址空间、页表等。

现在有这样的一个技术,我们可以在进程内部创建多个task_struct,并且这些task_struct共享该进程的地址空间、页表等。而这些一个个的task_struct就是线程(执行流)。

在Linux内核中只给进程设计了专门的数据结构,而没有给线程设计专门的数据结构,因为Linux的大佬认为进程和线程在很多处理上都是一样的,没必要再给线程设计专门的数据结构,所以Linux中的线程结构很多都是复用的进程的。因此Linux系统下没有真正意义上的线程结构,而是用进程的机制模拟实现的线程。而Windows系统中有线程相关的数据结构,所以Windows的结构更复杂

综上对于线程更准确的定义是:线程是一个进程内部的控制序列(task_struct)

线程在进程内部运行,本质是在进程地址空间内运行。

线程的异常 

  • 单个线程如果出现除零、野指针问题导致崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随之退出。
  • 如果在线程中调用exec系列函数或者exit函数也会导致给进程退出。

可以理解为:进程和线程是一体的,所以一荣俱荣,一损俱损

🔥进程VS线程 

进程是资源分配的基本单位,而线程是调度的基本单位。

线程虽然共享进程的数据,但需要有自己的一部分数据:线程ID、寄存器、栈、errno、信号屏蔽字、调度优先级等。

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

  • 文件描述符表
  • 每种信号的处理方式
  • 当前的工作目录
  • 用户ID和组ID

有了线程的概念之后,在看待之前的进程时,就可以理解为内部只有一条执行流的进程。 

在Linux系统中,CPU是不区分进程和线程的,因为线程也是用进程的机制来模拟实现其功能的,但在CPU看来具有多条执行流的进程比单执行流的进程更加的轻量化,原因如下:

  • 在CPU内部是有缓存的,当执行程序时,是需要先将内存中的代码和数据预读进缓存的。
  • 当一个进程的时间片到了需要被切走时,要把进程对应的上下文数据全部保存起来再切走,并且地址空间、页表等也都需要被切走,而此时的缓存也立即失效了,当下一个进程来了,就需要重新将自身的代码和数据预读进缓存。
  • 而当一个线程的时间片到了需要被切走时,只需要将自己的上下文数据保存起来切走就行了,也不需要重新将代码和数据预读进缓存。

所以Linux下的线程也会被称为轻量级进程。 

进程和线程的关系如下图

线程控制 

因为Linux中是用进程机制模拟实现的线程的,所以Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口,但在用户层给我们实现了一套多线程的方案,并且以库的方式提供给用户进行使用——pthread线程库(原生线程库)

关于线程库的一些了解

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

🔥线程相关函数 

线程创建

参数

  • thread:返回线程ID
  • attr:设置线程的属性,attr为nullptr表示使用默认属性
  • start_routine:是一个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数

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

示例代码 

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdio>
#include <unistd.h>
using namespace std;

void *threadRun(void *args)
{
    const string name = (char *)args;
    while (true)
    {
        cout << name << ", pid: " << getpid() << 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 + 1);
        pthread_create(tid + i, nullptr, threadRun, (void *)name);
        sleep(1);//缓解传参的bug问题
    }

    while (true)
    {
        cout << "main thread, pid: " << getpid() << endl;
        sleep(3);
    }
    return 0;
}

结果演示

其中ps -aL命令可以用来查看线程

LWP就是轻量级进程的意思

从结果就可以看出,每次线程的顺序都是不一样的,这是因为调度器调度的问题。 

一旦一个线程出现了异常,那么整个进程就会退出。

示例代码

void *threadRoutine(void *args)
{
    int i = 0;
    while (true)
    {
        cout << "这是新线程: " << (char *)args << " running..." << endl;
        sleep(1);
        int a = 10;
        a /= 0;//除0错误
    }
    cout << "新线程退出..." << endl;
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    return 0;
}

结果演示

线程等待

没错线程也是需要等待的,因为如果主线程不等待,即会引起类似于僵尸问题,导致内存泄漏。

参数:

  • thread:线程ID
  • retval:指向一个指针,该指针指向线程退出的返回值

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

示例代码 

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdio>
#include <unistd.h>
using namespace std;

void *threadRoutine(void *args)
{
    int i = 0;
    while(true)
    {
        cout << "这是新线程: " << (char*)args << " running..." << endl;
        sleep(1);
        if(i++ == 10) break;
    }
    cout << "新线程退出..." << endl;
    return (void*)10;//线程退出的返回值
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void* ret = nullptr; 
    pthread_join(tid, &ret);
    cout << "main thread wait done ... mian quit ... new thread quit: " << (long long)ret << endl;
    return 0;
}

结果演示

不只是可以返回一个数字,返回一组数也是可以的。

示例代码 

void *threadRoutine(void *args)
{
    int i = 0;
    int *data = new int[10];
    while (true)
    {
        cout << "这是新线程: " << (char *)args << " running..." << endl;
        sleep(1);
        data[i] = i;
        if (i++ == 10)
            break;
    }
    cout << "新线程退出..." << endl;
    return (void *)data; // 线程退出的返回值
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    int *ret = nullptr;
    pthread_join(tid, (void**)&ret);
    cout << "main thread wait done ... mian quit ... new thread quit:" << endl;
    for (int i = 0; i < 10; i++)
    {
        cout << ret[i] << " ";
    }
    cout << endl;
    return 0;
}

结果演示

线程终止

如果需要终止某个线程而其它线程不受影响,可以有三种方法:

  1. return,这种方法对主线程不适用,在main函数中return相当于调用exit。
  2. 调用pthread_exit终止自己
  3. 在一个线程中调用pthread_cancel终止同一个进程中的另一个线程。

参数:

  • retval:和线程等待那的参数一样

返回值:无返回值


参数:

  • thread:线程ID

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

调用pthread_cancel终止的话,那么pthread_join等待接收到的就是常数PTHREAD_CANCELED。

示例代码

void *threadRoutine(void *args)
{
    int i = 0;
    while (true)
    {
        cout << "这是新线程: " << (char *)args << " running..." << endl;
        sleep(1);
        if (i++ == 5)
            break;
    }
    cout << "新线程退出..." << endl;
    pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");

    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << "main thread wait done ... mian quit ... new thread quit:" << (long long)ret << endl;
    return 0;
}

结果演示 

获取线程ID 

返回值:线程ID

pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,但是这个线程ID和ps -aL命令查看的ID是不一样的,用ps -aL查看的ID是属于进程调度范畴的,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程。而pthread_create参数中的线程ID是指向一个虚拟内存单元(这个单元是在地址空间中的共享区),该内存单元的地址即为新创建线程的线程ID。

Linux线程库中提供了pthread_self函数用来获取线程自身的ID,其本质是一个地址。

如图所示: 

示例代码

void *threadRoutine(void *args)
{
    cout << "pthread_self:" << pthread_self() << endl;
    cout << "这是新线程: " << (char *)args << " running..." << endl;
    pthread_exit((void*)10);
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    cout << "tid:" << tid << endl;

    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << "main thread wait done ... mian quit ... new thread quit:" << (long long)ret << endl;
    return 0;
}

结果演示 

线程的局部存储  

每个线程都可以拥有自己的数据,使用__thread修饰全局变量就可以让每个线程中都各自拥有一个全局变量,这就实现了线程的局部存储。

示例代码

__thread int g_val = 10; //__thread修饰全局变量:线程的局部存储,让每一个线程各自拥有一个全局变量

void *threadRoutine(void *args)
{
    
    while (true)
    {
        cout << (char *)args << " : " << g_val << " 地址: " << &g_val << endl;
        g_val++;
        sleep(1);
        break;
    }
    return nullptr;
}

int main()
{
    pthread_t tid; // 本质上是一个地址,各自线程的独立属性(独立的栈、id等)
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    while (true)
    {
        cout << "main thread: " << g_val << " 地址: " << &g_val << endl;
        sleep(1);
        break;
    }
    pthread_join(tid, nullptr);
    return 0;
}

结果演示

线程分离

在一些场景下,如果我们不关心该线程的退出结果,那么我们对线程进程pthread_join就是一种负担,所以就可以使用线程分离函数pthread_detach,告诉操作系统,让这个线程退出时自动释放资源。 

 

参数:

  • thread:线程ID

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

可以是线程组内其它线程对目标线程进行分离,也可以是线程自己分离。

示例代码

void *threadRoutine(void *args)
{
    pthread_detach(pthread_self()); // 线程分离
    cout << "新线程退出了,并且自己释放了资源" << endl;
    return nullptr;
}

int main()
{
    pthread_t tid; 
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread 1");
    while (true)
    {
        sleep(1);
        break;
    }
    int n = pthread_join(tid, nullptr);
    cout << "n: " << n << " strerr: " << strerror(n) << endl;//因为线程自己释放了资源,所以pthread_join会失败
    return 0;
}

结果演示

 

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

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

相关文章

php反序列化入门

一&#xff0c;php面向对象。 1.面向对象&#xff1a; 以“对象”伪中心的编程思想&#xff0c;把要解决的问题分解成对象&#xff0c;简单理解为套用模版&#xff0c;注重结果。 2.面向过程&#xff1a; 以“整体事件”为中心的编程思想&#xff0c;把解决问题的步骤分析出…

实时监控电脑屏幕的软件是什么?三款超受欢迎的电脑监控软件

实时监控电脑屏幕的软件在现代企业管理中扮演着至关重要的角色&#xff0c;它们不仅帮助管理者实时监控员工的工作状态&#xff0c;提高工作效率&#xff0c;还通过数据分析和报告功能&#xff0c;为企业提供了优化管理流程和决策支持的依据。以下将介绍几款市面上广泛使用的实…

Redis过期策略数据淘汰策略

过期策略 一、设置过期时间 redis有四种命令可以用于设置键的生存时间和过期时间&#xff1a; EXPIRE : 将键的生存时间设为 ttl 秒 PEXPIRE :将键的生存时间设为 ttl 毫秒 EXPIREAT :将键的过期时间设为 timestamp 所指定的秒数时间戳 PEXPIREAT : 将键的过期时间设为 times…

GNU Radio创建qt time plot python OOT块

文章目录 前言一、创建自定义的 OOT 块1、安装相应依赖2、创建 OOT 块3、修改相关4、编译及安装 OOT 块 二、测试1、grc 图2、运行结果 三、资源自取 前言 官方提供的绘制时域波形的 block 名字叫做 QT GUI Time Sink&#xff0c;其底层实现是用 C 写的&#xff0c;但是我发现…

virtualbox中ubuntu22.04网络配置

第一&#xff1a;添加两个网卡&#xff0c;网卡1是NAT方式&#xff0c;网卡2是仅主机模式&#xff08;两个顺序不能颠倒&#xff09; 第二步&#xff1a;启动ifconfig查看网络

『 Linux 』文件系统

文章目录 磁盘构造磁盘抽象化 磁盘的寻址方式磁盘控制器磁盘数据传输文件系统Inode数据块(Data Blocks)超级块(SuperBlock)块组描述符(Group Descriptor) 磁盘构造 磁盘内部构造由磁头臂,磁头,主轴,盘片,盘面,磁道,柱面,扇区构成; 磁头臂&#xff1a;控制磁头的移动,可以精确地…

Exce 两列一组对齐呈现,缺失补 0

Excel 里有 多 组数据&#xff0c;每组 2 列&#xff0c;每组长度不同。第 1 列是编号&#xff0c;列之间的编号有重复。 ABCDEFGH1Mass10Mass11Mass12Mass132802200581309088146532802225938133306824779282975598142002482273148413154988335698822331305832720485110460842…

go解析yaml

go解析yaml文件关键就是结构体的创建 初学go tag字段要和yaml文件中的key对应起来&#xff0c;每个层级都要创建对应的结构体&#xff0c;有点烦 package configimport ("gopkg.in/yaml.v3""os" )type Config struct {MysqlConfig MysqlConfig yaml:&q…

Spring Boot 开发 -- 过滤器与拦截器详解

引言 在Web开发中&#xff0c;经常需要对请求进行预处理或在响应后进行后处理&#xff0c;Spring Boot提供了过滤器和拦截器两种机制来实现这一需求。虽然它们都可以用来处理HTTP请求和响应&#xff0c;但在使用场景、执行顺序和配置方式上存在明显的差异。本文将详细讲解Spri…

【UML用户指南】-01-UML基本元素的介绍(一)

1、UML的词汇表 &#xff08;1&#xff09;事物&#xff1b; &#xff08;2&#xff09;关系&#xff1b; &#xff08;3&#xff09;图。 事物是对模型中首要成分的抽象&#xff1b;关系把事物结合在一起&#xff1b;图聚集了相关的事物。 注&#xff1a;事物也称为元素 2…

东芝机械人电池低报警解除与机器人多旋转数据清零

今天启动一台设备&#xff0c;触摸屏一直显示机器人报警&#xff08;翻译过后为电池电量低&#xff09;&#xff0c;更换电池后关机重启后也不能消除&#xff0c;所以打开示教器&#xff0c;下面就来说说怎么解决此项问题&#xff08;可以参考官方发的手册&#xff0c;已手册为…

携程梁建章:持续投资创新与AI,开启旅游行业未来增长

5月30至31日&#xff0c;携程集团在上海和张家界举办Envision 2024全球合作伙伴大会&#xff0c;邀请超50个国家和地区的1600余名外籍旅游业嘉宾与会&#xff0c;共同探讨中国跨境旅游市场发展机遇&#xff0c;讲好中国故事。 携程国际业务增速迅猛&#xff0c;创新与AI解锁未…

IntelliJ IDEA / Android Studio 方法显示Git提交人

显示方法&#xff1a; 设置 > 编辑器 > 嵌入提示 > Code Vision > 代码作者&#xff08;勾选&#xff09; IntelliJ IDEA Android Studio

css-表头筛选的特定样式

背景 饿了么的表头筛选样式比较简单&#xff0c;如图1&#xff0c;产品觉得不够醒目&#xff08;觉得用户可能不知道这是筛选&#xff0c;我表示不理解&#xff09; 要求改进筛选的样式&#xff0c;达到图2的效果&#xff0c;主要是状态列&#xff0c;既希望这列的宽度固定&a…

git应用最佳实践

插&#xff1a; AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家(前言 – 人工智能教程 ) 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

Linux内网中安装jdk1.8详细教程

本章教程,主要介绍如何在内网环境中配置JDK1.8环境变量 一、下载Linux版压缩包 下载地址:https://www.oracle.com/java/technologies/downloads/#java8 下载完成之后,通过XFTP等工具,将安装包上传到内网服务器 二、安装配置步骤 1、解压压缩包 tar -zxvf /usr/local/jdk-…

jpeg编码学习

正点原子stm32教程提到过jpeg解码库libjpeg&#xff0c;但是没有提到jpeg编码&#xff0c;我也好奇jpeg编码怎么实现&#xff0c;用代码怎么生成jpeg文件的。所以最近学习了jpeg编码&#xff0c;在这里做记录。 参考文章 jpeg图片格式详解 https://blog.csdn.net/yun_hen/art…

【嵌入式DIY实例】-OLED显示网络时钟

OLED显示网络时钟 文章目录 OLED显示网络时钟1、硬件准备与接线2、代码实现在上一个ESP8266 NodeMCU文章中,我们用DS3231 RTC芯片和SSD1306 OLED制作了一个简单的实时时钟,时间和日期显示在SSD1306屏幕上,并且可以通过两个按钮进行设置。 在本中,我们将使用ESP 8266 NodeMC…

SpringBoot 多模块 多环境 项目 单元测试

环境描述 假设项目中有以下三个yml文件&#xff1a; application.ymlapplication-dev.ymlapplication-prod.yml 假设项目各Module之间依赖关系如下&#xff1a; 其中&#xff0c;D依赖C&#xff0c;C依赖B&#xff0c;B依赖A&#xff0c;D对外提供最终的访问接口 现在要想采…

glpi 安装与使用

1、环境介绍 操作系统&#xff1a;龙蜥os 8.9 nginx&#xff1a;1.26.1 php&#xff1a;8.2.19 mysql&#xff1a;MarinaDB 10.3.9 glpi&#xff1a;10.0.6 fusioninventory&#xff1a;fusioninventory-10.0.61.1 2、安装epel源 dnf install epel-release -y dnf install htt…