【Linux】线程

devtools/2025/3/14 10:18:29/

在这里插入图片描述

文章目录

  • 线程(Thread)
    • 1. 什么是线程?
  • 创建线程
  • 多线程中的重入问题
  • 线程异常
  • 线程等待
  • 总结

线程(Thread)

1. 什么是线程?

线程是进程中的一个执行单元,它是 CPU 调度的基本单位。线程依赖于进程存在,一个进程可以包含多个线程,这些线程可以并发执行,提高程序的运行效率。

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

线程是操作系统调度的基本单位

用一张图简要说明一下什么是线程:
在这里插入图片描述
首先我们要知道,在Linux中是没有实际的线程的,线程是被模拟出来的,Linux实际上使用LWP模拟的线程。
LWP(Light Weight Process,轻量级进程)是 Linux 线程实现的一种机制,它与传统进程共享大部分资源,但仍有自己的调度信息。

创建线程

在这里插入图片描述
pthread_create是用于创建线程的函数,这个函数不是系统调用,因为Linux实际上是没有实体的线程,这个创建线程的函数是在pthread.h中封装的函数。
这个函数的第一个参数是pthread_t*类型的,pthread_t表示线程id,第二个参数表示线程属性,要是我们不观星的话,就直接传递nullptr,第三个参数是线程执行函数,就是函数指针,新线程就是从这个函数的入口开始执行的,第四个参数是传递给执行新线程的函数的参数。

代码展示:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void *routine(void* args)
{string name = static_cast<const char*>(args);while(true){cout << "I am new thread,the name is:" << name << endl;sleep(1);}
}int main()
{pthread_t id;pthread_create(&id,nullptr,routine,(void*)"thread-1");while(true){cout << "I am main thread" << endl;sleep(1);}return 0;
}

如果真的创建了一个新线程,那么routine函数也会跑起来,这里有两个执行流。
在这里插入图片描述

可以看见确实有两个执行流。
创建线程之后,我们来讨论一下这个id是什么,没错就是线程id,我们打印一下:
在这里插入图片描述
可以看到转化为16进制之后打印出来的是这个,如何用函数获得线程id呢?在库中有一个函数可以获取线程的id:
在这里插入图片描述
这个函数不用传任何参数,可以获取本线程的id,我们来测试一下这个函数的正确性:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;//转化为十六进制
string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%lx",tid);return buffer;
}void *routine(void* args)
{string name = static_cast<const char*>(args);while(true){cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;sleep(1);}
}int main()
{pthread_t id;pthread_create(&id,nullptr,routine,(void*)"thread-1");cout<<toHex(id)<<endl;while(true){cout << "I am main thread" << endl;sleep(1);}return 0;
}

在这里插入图片描述
可以看到结果是一致的。
我们现在已经创建了一个新的线程,意思就是我们现在有两个线程,我们来查一下:
不加任何选项查线程只能查出进程。
在这里插入图片描述

ps -aL

在这里插入图片描述
可以看见确实有两个线程,两个线程的pid是相同的,那哪一个是主线程,哪一个是新线程呢?pid和lwp相同的是主线程,pid和lwp不同的是新线程。

多线程中的重入问题

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;//转化为十六进制
string toHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%lx",tid);return buffer;
}void *routine(void* args)
{string name = static_cast<const char*>(args);while(true){cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;sleep(1);}
}int main()
{pthread_t id1;pthread_create(&id1,nullptr,routine,(void*)"thread-1");pthread_t id2;pthread_create(&id2,nullptr,routine,(void*)"thread-2");pthread_t id3;pthread_create(&id3,nullptr,routine,(void*)"thread-3");pthread_t id4;pthread_create(&id4,nullptr,routine,(void*)"thread-4");while(true){cout << "I am main thread" << endl;sleep(1);}return 0;
}

我们可以同时创建多个线程:
在这里插入图片描述
我们运行之后,可以看见,有时候显示器屏幕上会出现混乱的现象,这是因为当我们创建多个线程的时候,这多个线程同时访问routine函数,routine函数就被重入了,因为routine函数中有cout这个io函数,这个函数的本质其实是在访问显示器文件,在向显示器文件上写入,所以这里显示器文件是公共资源,被多个线程访问,因为这里的公共资源没有加上保护,所以每个线程想怎么打印就怎么打印,所以有时候会出现混乱的现象。

线程异常

当某一个线程出现野指针或者除0等错误异常时,整个进程都会退出,这是因为,当某一个线程触发异常时,在CPU当中会触发中断,触发中断后,会拿着中断去找处理方法,找到之后,就会处理信号,因为信号的处理单位时进程,所以处理信号的时候会直接改写每个线程的信号表,将每个线程直接杀死。

线程等待

不光是进程需要等待,线程也是需要等待的,线程和进程一样,默认等待方式时阻塞等待。

线程等待函数:
在这里插入图片描述
第一个参数是线程的id,第二个参数是二级指针,如果我们不关心退出结果,那么我们可以直接将第二个参数设置为nullptr。

void *routine(void* args)
{string name = static_cast<const char*>(args);while(true){cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;sleep(1);break;}
}int main()
{pthread_t id1;pthread_create(&id1,nullptr,routine,(void*)"thread-1");pthread_join(id1,nullptr);return 0;
}

我们让新线程跑一秒钟,然后跑完直接退出,看看会是什么结果。
在这里插入图片描述
可以看见跑完一秒钟后直接就退出了。
假如我们关心退出结果呢?
在这里插入图片描述

在这里插入图片描述
我们打印一下退出结果,可以看见,退出结果就是新线程的返回值。

这里我们可以来通过返回值验证一下堆空间是线程之间共享的。

void *routine(void* args)
{string name = static_cast<const char*>(args);while(true){cout << "I am new thread,the name is:" << name <<",this thread's id is:" << toHex(pthread_self()) <<endl;sleep(1);break;}int *p = new int(10);return (void*)p;
}int main()
{pthread_t id1;pthread_create(&id1,nullptr,routine,(void*)"thread-1");void* ret = nullptr;pthread_join(id1,&ret);cout<<*(int*)ret<<endl;return 0;
}

在这里插入图片描述
这类我们在新线程中new一个空间,线程退出之后,主线程是可以访问到new出的地址的,new是在堆空间中开辟的,所以这里可以看出堆空间是共享的,其实如果我们想的话,栈空间也是可以共享的,只需要在新线程中创建一个int变量,然后创建一个全局变量,int*的,然后然后在新线程中,把那个变量的地址给全局变量,其实在主线程中,我们是可以修改这个变量的。所以也验证了堆空间,虽然没办法共享,但是也是可以想办法看到的。
在这里插入图片描述

总结

在本篇文章中,我们深入探讨了线程的基本概念、创建方式、多线程中的重入问题、线程异常处理以及线程等待机制。通过这些内容,我们可以更好地理解线程的工作方式,并在实际开发中合理运用多线程技术,提高程序的并发性能。

多线程编程虽然能够提升程序的执行效率,但也伴随着诸多挑战,如数据竞争、死锁、线程安全等问题。因此,在使用多线程时,我们需要充分考虑同步与互斥机制,合理设计程序结构,以确保线程安全性和稳定性。

希望本篇文章能帮助你更好地理解多线程的基础知识,并在实践中灵活应用。如果你有更多问题或想深入学习,可以继续探索线程池、锁优化、异步编程等更高级的内容。


http://www.ppmy.cn/devtools/166996.html

相关文章

大型语言模型在工业应用中的局限性:事实性扩充与深入分析

大型语言模型在工业应用中的局限性&#xff1a;事实性扩充与深入分析 摘要 本文深入探讨了大型语言模型&#xff08;LLMs&#xff09;在工业应用中所面临的重大挑战&#xff0c;特别聚焦于其在机械图纸解读、可编程逻辑控制器&#xff08;PLC&#xff09;程序生成以及更广泛的…

数据类型与变量

目录 1、字面常量2、数据类型2.1、基本数据类型2.2、引用数据类型 3、变量3.1、整型变量3.2、浮点型变量3.3、字符型变量3.4、布尔型变量3.5、类型转换3.6、类型提升 1、字面常量 程序运行期间&#xff0c;固定不变的量是常量。 字母常量的分类&#xff1a; 字符串常量&#…

PostgreSQL存储管理体系结构学习笔记2

1.表和元组的组织方式 在PostgreSQL中&#xff0c;同一个表中的元组按照创建顺序依次插入到表文件中。元组之间不进行关联&#xff0c;这样的表文件称之为堆文件。PostgreSQL系统中包含了四种堆文件&#xff1a;普通堆&#xff0c;临时堆&#xff0c;序列&#xff0c;TOAST表。…

Operator <=> (spaceship operator)

operator <>动机 在C20以前定义比较运算符&#xff1a;其他比较运算符基于<和实现 struct Type {int value;// 相等运算符friend bool operator(const Type& a, const Type& b) {return a.value b.value;}// 不等运算符friend bool operator!(const Type&a…

【H2O2 | 软件开发】Axios发送Http请求

目录 前言 开篇语 准备工作 正文 概念 封装工具包 示例 结束语 前言 开篇语 本系列为短篇&#xff0c;每次讲述少量知识点&#xff0c;无需一次性灌输太多的新知识点。该主题文章主要是围绕前端、全栈开发相关面试常见问题撰写的&#xff0c;希望对诸位有所帮助。 如…

iPaaS集成平台轻量化架构的重要性

在数字化转型的浪潮中&#xff0c;企业对于高效、灵活、可扩展的集成平台的需求日益增长。iPaaS&#xff08;集成平台即服务&#xff09;作为云计算领域的创新解决方案&#xff0c;正逐渐成为企业解决集成难题的首选。而在这其中&#xff0c;轻量化架构成为了iPaaS集成平台的核…

【存储中间件】Redis核心技术与实战(一):Redis入门与应用(常用数据结构:字符串String、哈希Hash、列表List)

文章目录 Redis入门与应用Redis常用数据结构字符串&#xff08;String&#xff09;操作命令set 设置值get 获取值mset 批量设置值mget 批量获取值Incr 数字运算append追加指令strlen 字符串长度getset 设置并返回原值setrange 设置指定位置的字符getrange 截取字符串 命令的时间…

JVM垃圾收集器合集

前言&#xff1a;JVM GC收集器的回顾与比较 JVM&#xff08;Java虚拟机&#xff09;中的垃圾收集器是自动管理内存的重要机制&#xff0c;旨在回收不再使用的对象所占用的内存空间。以下是JVM中几种常见的垃圾收集器的详细介绍&#xff1a; 一、新生代垃圾收集器 1.Serial收集…