Linux下多线程的相关概念

news2024/10/6 18:30:13

🤖个人主页:晚风相伴-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;
}

结果演示

 

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

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

相关文章

py黑帽子学习笔记_scapy

简介 代码简洁&#xff1a;相比于前两个博客总结&#xff0c;很多socket操作&#xff0c;如果使用scapy仅需几行代码即可实现 获取邮箱身份凭证 编写基础嗅探器&#xff0c;脚本可显示任何收到的一个包的详细情况 直接运行 尝试监听邮件收发&#xff0c;监听指定端口&#x…

2024年03月 Python(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,共50分) 第1题 以下选项中,创建类正确的是?() A: class test1: def prt(self): …… B: class Mg(): def__init__(na,ag): self.na=na C: class A(): def print(self): print(“Yes”) a=A() a.print() D…

理解多线程看这一篇就够了

一、基本概念与关系 程序 程序是含有指令和数据的文件&#xff0c;静态地存储在磁盘等存储设备上。它是软件的实体&#xff0c;但未被激活。 进程 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位。当程序被操作系统加载并执行时&#xff0c;就成为一个进程&a…

Kprobe实现原理

kprobe其实就是将某个要检测的指令备份&#xff0c;再替换成int3(x86)或者未定义指令(arm)来触发异常&#xff0c;再调用对应体系的异常处理函数来执行我们自定义的hook&#xff0c;执行完我们自定义的hook,再将备份的指令放回原来的位置继续往下执行 下面我们就来看下linux内核…

拍视频麦克风什么牌子好?户外无线麦克风哪个牌子好,看本期文章

​无线领夹麦克风&#xff0c;作为现代音频技术的重要代表&#xff0c;已经广泛应用于各种场合。它不仅能提高演讲者的声音质量&#xff0c;还能增加演讲的互动性和生动性。然而&#xff0c;面对市场上众多的无线领夹麦克风产品&#xff0c;如何选择一款适合自己的设备成为了一…

TPshop商城的保姆教程(windows)

提前准备 phpStudy下载&#xff1a;https://www.xp.cn/download.html 选择适合自己的版本下载 TPshop商城源文件下载链接&#xff1a; https://pan.baidu.com/s/143fLrxbwe9CTMCbyx7mXJQ?pwd6666 开始安装 安装完phpstudy后 以管理员的身份启动phpstudy.exe 选择合适自己…

C语言 指针——指针变量的定义、初始化及解引用

目录 指针 内存如何编址&#xff1f; 如何对变量进行寻址&#xff1f; 用什么类型的变量来存放变量的地址? 如何显示变量的地址?​编辑 使用未初始化的指针会怎样&#xff1f; NULL是什么&#xff1f; 如何访问指针变量指向的存储单元中的数据&#xff1f; 指针变量的…

二刷算法训练营Day15 | 二叉树(2/9)

目录 详细布置&#xff1a; 1. 层序遍历 2. 226. 翻转二叉树 3. 101. 对称二叉树 详细布置&#xff1a; 1. 层序遍历 昨天练习了几种二叉树的深度优先遍历&#xff0c;包括&#xff1a; ​​​​​​前中后序的递归法前中后序的迭代法前中后序迭代的统一写法 今天&…

糖尿病视网膜病变分级新方法:卷积网络做分割和诊断 + 大模型生成详细的测试和治疗建议

糖尿病视网膜病变分级新方法&#xff1a;卷积网络做分割和诊断 大模型生成详细的测试和治疗建议 提出背景相关工作3.1 数据集3.1.1 病变分割 3.1.2 图像分级3.1.3 大型语言模型&#xff08;LLMs&#xff09; 解法 数据预处理 数据增强 网络架构 训练过程 测试过程子解法1…

spdlog日志库源码:线程池thread_pool

线程池 线程池本质上一组事先创建的子线程&#xff0c;用于并发完成特定任务的机制&#xff0c;避免运行过程中频繁创建、销毁线程&#xff0c;从而降低程序运行效率。通常&#xff0c;线程池主要涉及到以下几个方面问题&#xff1a; 如何创建线程池&#xff1f;线程池如何执…

FPGA基础:触发器和锁存器

目录 锁存器&#xff08;Latch&#xff09;D触发器&#xff08;Flip-Flop&#xff09;最基本时序电路时序块&#xff08;Sequential blocks&#xff09;:同步与异步触发器概念触发器分类触发器的Verilog实现1. 上升沿触发的触发器2. 带异步复位、上升沿触发的触发器3. 带异步复…

Pandas-中axis的用法

在Pandas中&#xff0c;min(axis)方法是计算DataFrame或Series中每行或每列的最小值的函数。该函数可以接受一个参数axis&#xff0c;用于指定计算最小值的方向。当axis0时&#xff0c;表示沿着行的方向计算最小值&#xff1b;当axis1时&#xff0c;表示沿着列的方向计算最小值…

奶奶也能看懂的耦合协调度分析

不会计算&#xff1f;跟着文献学起来~ 案例数据连接&#xff08;复制链接后粘贴到浏览器中&#xff09;&#xff1a; 耦合协调度数据​spssau.com/spssaudata.html?shareDataF363000CD033FF15E557BB75B9B0D412 假如你有这样一组数据&#xff1a; 如何进行计算分析耦合协调度…

python中pow是什么意思

pow()方法返回xy&#xff08;x的y次方&#xff09;的值。 语法 以下是math模块pow()方法的语法&#xff1a; import math math.pow( x, y ) 内置的pow()方法 pow(x, y[, z]) 函数是计算x的y次方&#xff0c;如果z在存在&#xff0c;则再对结果进行取模&#xff0c;其结果等效…

【后端开发】服务开发场景之分布式(CAP,Raft,Gossip | API网关,分布式ID与锁 | RPC,Dubbo,Zookeeper)

【后端开发】服务开发场景之分布式&#xff08;CAP&#xff0c;Raft&#xff0c;Gossip | API网关&#xff0c;分布式ID与锁 | RPC&#xff0c;Dubbo&#xff0c;Zookeeper&#xff09; 文章目录 1、如何设计一个分布式系统&#xff1f;&#xff08;底层原理&#xff09;理论&a…

企业网站有必要进行软件测试吗?网站测试有哪些测试流程?

企业网站在现代商业中扮演着重要的角色&#xff0c;它不仅是企业形象的重要体现&#xff0c;也是与客户、合作伙伴进行沟通与交流的重要渠道。然而&#xff0c;由于企业网站的复杂性和关键性&#xff0c;其中可能存在各种潜在的问题和隐患。因此&#xff0c;对企业网站进行软件…

网页中的音视频裁剪拼接合并

一、需求描述 项目中有一个配音需求&#xff1a; 1&#xff09;首先&#xff0c;前台会拿到一个英语视频&#xff0c;视频的内容是A和B用英语交流&#xff1b; 2&#xff09;然后&#xff0c;用户可以选择为某一个角色配音&#xff0c;假如选择为A配音&#xff0c;那么视频在播…

Centos7时区设置及手动修改时间

一、修改系统时区 1、查看时区命令 timedatectl 2、设置时区命令 #下面将时区设置为上海时区 timedatectl set-timezone Asia/Shanghai 3、查看时区看一下新时区有没有生效 timedatectl 二、手动修改系统时间 修改系统时间 date -s "2023-12-25 16:05:10" 查…

display: none 和 visibility: hidden 的共性与区别

display: none 和 visibility: hidden 的共性与区别 共性&#xff1a;display: none 和 visibility: hidden 都是用于设置元素可见性的样式 区别 display: none 使元素及其占位完全消失&#xff1a;元素及其所有子元素将从文档流和布局中完全消失&#xff0c;就像它们不存在一…

对于高速信号完整性,一块聊聊啊(17)

再来对前仿和后仿的仿真内容回顾一下&#xff1a; 从概念上有个根本的理解 前仿真又可以分为布局前仿真和布局后仿真。前者是在设计的最初阶段&#xff0c;建立和验证详细的电气拓扑结构并以此制定出详细的约束规则。后者是在布局完成的状态下&#xff0c;在布线过程中遇到的…