Linux之多线程(下)——线程控制

news/2025/3/15 4:35:41/

文章目录

  • 前言
  • 一、POSIX线程库
    • 1.概念
    • 2.pthread线程库是应用层的原生线程库
    • 3.错误的检查
  • 二、线程控制
    • 1.创建线程——pthread_create
      • pthread_create函数
      • 例子
        • 创建一个新线程
        • 主线程创建一批新线程
    • 2.获取线程ID——pthread_self
    • 3.线程等待——pthread_join
    • 4.线程终止——return、pthread_exit、pthread_cancel
      • return
      • pthread_exit
      • pthread_cancel
    • 5.分离线程——pthread_detach
      • pthread_detach函数
      • 例子
  • 总结


前言

本文介绍了Linux下的线程控制。


一、POSIX线程库

1.概念

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

2.pthread线程库是应用层的原生线程库

我们在Linux之多线程(上)这篇文章中了解:在Linux中没有真正意义上的线程,因此系统无法直接给我们提供创建线程的系统接口,只能提供创建轻量级进程额度接口。
在用户角度,当我们想创建一个线程时会想使用thread_sreate这样的接口,而不是像vfork这样的函数。用户不能直接访问OS,所以OS在用户和系统调用之间提供了编写好的用户级线程库,这个库一般称为pthread库。任何Linux操作下系统都必须默认携带这个库,因此这个库也称为原生线程库。
原生线程库本质上是对轻量级进程的系统调用(clone)做了封装——pthread_create,用户层也因此模拟实现了一套线程相关的接口。
用户眼中的线程实际上会在OS内部被转化为轻量级进程。

3.错误的检查

传统的函数,成功就返回0,失败返回-1,并且给全局变量errno赋错误码以指示错误。
pthread函数出错时并不会设置全局变量errno(大部分其他POSIX函数会设置),而是讲错误码通过返回值返回。当然,pthread函数是提供了线程内的errno变量,以支持其他使用errno的代码。对于pthread函数的错误,建议通过返回值判定,因为读取返回值比读取线程内的errno变量的开销更小

二、线程控制

1.创建线程——pthread_create

pthread_create函数

在这里插入图片描述
参数:

  1. thread:获取线程的ID,该参数是输出型参数;
  2. attr:用于设置创建线程的属性,传入nullptr表示默认,这个属性一般不用管直接传nullptr就行;
  3. start_routine:函数地址,表示线程启动后要执行的函数;
  4. arg:传给线程例程的参数。

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

例子

创建一个新线程

文件mythread.cc

  1 #include<iostream>2 #include<string>3 #include<unistd.h>4 #include<pthread.h>5 #include<assert.h>6 using namespace std;7 void* thread_routine(void* args)8 {9         string name = static_cast<const char*>(args);//安全的进行强制类型转换10         while(1)11         {12                 cout<<"这是新线程, name:"<<name<<endl;13                 sleep(1);14         }15 }16 int main()17 {18         pthread_t id;19         int n = pthread_create(&id, nullptr, thread_routine, (void*)"thread new");20         assert(n == 0);21         (void)n;22         while(1)23         {24                 cout<<"我是主线程,我正在运行"<<endl;25                 sleep(1);26         }27         return 0;28 }

在这里插入图片描述
这里编译运行需要注意的是,pthread_create接口是库提供给我们的,我们使用的接口如果不是语言上的接口或者操作系统的接口,而是库提供的接口,那么在编译的时候是无法通过的,需要链接这个库才能编译成功。要链接这个库首先要找到这个库,-L:找到库在哪里;-l:找到头文件在哪里,库已经在系统中安装好了,所以除了高所系统库和头文件在哪里以外,还要知道是链接哪一个库(库名字)。
所以要加上-lpthread
此时我们用ps axj命令查看当前进程的信息时,虽然此时该进程中有两个线程,但是我们只能看到一个进程,因为这两个线程是属于一个进程的:

要想查看到轻量级进程需要使用ps -aL指令:

其中LWP(Light Weight Process)表示的是轻量级进程的ID,可以看到显示出的两个轻量级进程的PID是相同的(因为它们属于同一个进程),而每个轻量级进程都有唯一的LWP。
注意:主线程的PID和LWP是相同的,PID和LWP不相同的是新线程,所以CPU进行调度时,是以LWP为标识符进行标定一个线程执行流。
线程一旦被创建,几乎所有的资源都是被所有线程所共享的,因此线程之间想要进行交互是很容易的,因为直接就可以看到同一份资源。

线程要有自己的私有资源:
线程被调度就要有独立的PCB属性——LWP;
线程被切换时正在运行,需要进行上下文的保存,因此线程要有私有的上下文结构;
每个线程都要独立的运行,所以线程要有自己独立额度栈结构。

主线程创建一批新线程

让主线程一次性创建十个新线程,并让创建的每个新线程都去执行start_routine函数,即start_routine这个函数会被重复进入。并且start_routine函数是可重入函数(不会产生二义性),没有因为一个线程去影响另一个线程。在函数定义内定义的变量都是局部变量具有临时性,所以在多线程的情况下也没有问题。
文件mythread.cc

这也说明了每个线程都有自己独立的栈结构

2.获取线程ID——pthread_self

在这里插入图片描述
获取线程ID:1.创建线程时通过输出型参数获取;2.通过pthread_self接口函数获取。
我们可以通过主线程打印出新线程的ID,再通过新线程打印出自己的ID,判断是否相同。

结果是相同的。

3.线程等待——pthread_join

一个线程退出时和进程一样是需要等待的,如果线程不等待,对应的PCB没有被释放也会造成类似僵尸进程的问题(内存泄漏)。所以线程也需要被等待:1.获取新线程的退出信息;2.回收新线程对应的PCB等内核资源,防止内存泄漏。
在这里插入图片描述
参数:
thread是被都能打线程的ID;
retval:线程退出时的退出码。

void** retval:输出型参数,主要用于获取线程退出时返回的退出结果。之所以是void**,是因为如果想作为输出型结果返回就必须是void**(因为线程函数的返回结果是void*)

返回值:线程等待成功返回0,等待失败返回错误码。

没有看到线程退出时对应的退出码是因为线程出异常时收到信号,整个进程都会退出,而退出信息需要进程来关心,所以pthread_join默认会认为函数是调用成功的(等待成功),它不会考虑程序出现异常的情况,异常问题是进程该考虑的情况。

4.线程终止——return、pthread_exit、pthread_cancel

一个线程,如果只是想终止该线程而不是整个进程,有三种做法

  1. 直接从线程的函数结束,return就可以终止该线程;
  2. 线程可以自己调用pthread_exit终止自己;
  3. 一个线程可以调用pthread_cancel来终止同一个进程中的另一个线程。

return

pthread_exit

在这里插入图片描述

pthread_cancel

在这里插入图片描述

5.分离线程——pthread_detach

线程是可以等待的,等待的时候是join的等待(阻塞式等待)。如果我们不想等待:不去等待线程,而是进行分离线程处理。默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放该线程的资源,造成内存泄漏。
如果我们并不关心线程的返回值,此时join对我们来说是一种负担,这时,我们可以告诉OS,当线程退出时,自动释放线程资源,这种策略就是线程分离

pthread_detach函数

在这里插入图片描述

例子

创建新线程,让主线程与新线程运行起来,主线程等待新线程退出,等待完毕返回n。由于我们现在让新线程进行分离,那么按照理论此时主线程的等待结果是失败的。
文件mythread.cc

  1 #include<iostream>2 #include<unistd.h>3 #include<pthread.h>4 #include<string.h>5 using namespace std;6 #include<string>7 string changeld(const pthread_t& thread_id)8 {9         char tid[128];10         snprintf(tid, sizeof(tid), "0x%x", thread_id);11         return tid;12 }13 void* start_routine(void* args)14 {15         string name = static_cast<const char*>(args);16         pthread_detach(pthread_self());//线程分离,设置为分离状态17         int cnt = 5;18         while(cnt--)19         {20                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;21                 sleep(1);22         }23         return nullptr;24 }25 int main()26 {27         pthread_t tid;28         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");29         string main_id = changeld(pthread_self());30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;31         int n = pthread_join(tid, nullptr);32         cout<<"result:"<<n<<":"<<strerror(n)<<endl;33         return 0;34 }

运行:
在这里插入图片描述
但是我们发现等待结果依旧是成功的,这是为什么?
因为,我们创建新线程后,并不确定新线程和主线程哪个先被调度,所以可能导致我们还没有执行新线程的pthread_detach时,主线程就去等待新线程了。也就是说,新线程还没有来得及分离自己,主线程就去等待了。
因此我们可以让主线程sleep(2),保证新线程是分离的状态主线程再去等待,则此时等待是失败的。
文件mythread.c

  1 #include<iostream>2 #include<unistd.h>3 #include<pthread.h>4 #include<string.h>5 using namespace std;6 #include<string>7 string changeld(const pthread_t& thread_id)8 {9         char tid[128];10         snprintf(tid, sizeof(tid), "0x%x", thread_id);11         return tid;12 }13 void* start_routine(void* args)14 {15         string name = static_cast<const char*>(args);16         pthread_detach(pthread_self());//线程分离,设置为分离状态17         int cnt = 5;18         while(cnt--)19         {20                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;21                 sleep(1);22         }23         return nullptr;24 }25 int main()26 {27         pthread_t tid;28         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");29         string main_id = changeld(pthread_self());30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;31         sleep(2);32         int n = pthread_join(tid, nullptr);33         cout<<"result:"<<n<<":"<<strerror(n)<<endl;34         return 0;35 }

运行:
在这里插入图片描述

当然,我们也可以直接让主线程直接pthread_detach,而不是让新线程分离:线程运行起来就直接分离了,分离成功就去join了,此时新线程就去等待了。
文件mythread.c

  1 #include<iostream>2 #include<unistd.h>3 #include<pthread.h>4 #include<string.h>5 using namespace std;6 #include<string>7 string changeld(const pthread_t& thread_id)8 {9         char tid[128];10         snprintf(tid, sizeof(tid), "0x%x", thread_id);11         return tid;12 }13 void* start_routine(void* args)14 {15         string name = static_cast<const char*>(args);16         int cnt = 5;17         while(cnt--)18         {19                 cout<<name<<"is running..."<<changeld(pthread_self())<<endl;20                 sleep(1);21         }22         return nullptr;23 }24 int main()25 {26         pthread_t tid;27         pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");28         string main_id = changeld(pthread_self());29         pthread_detach(tid);//让主线程线程分离30         cout<<"main thread running... new thread id:"<<changeld(tid)<<"main thread id:"<<main_id<<endl;31         int n = pthread_join(tid, nullptr);32         cout<<"result:"<<n<<":"<<strerror(n)<<endl;33         return 0;34 }

在这里插入图片描述


总结

以上就是今天要讲的内容,本文介绍了线程控制相关的概念。本文作者目前也是正在学习Linux相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!


http://www.ppmy.cn/news/410061.html

相关文章

红米k30pro变焦版与华为nova7pro哪个好

红米K30pro变焦版屏幕方面搭载的是一块6.67英寸的AMOLED的屏幕&#xff0c;分辨率为1080*2400FHD&#xff0c;因为前置的隐藏式摄像头的设计&#xff0c;屏占比极高。拥有月幕白、天际蓝、太空灰、星环紫四种手机机身配色可以选择。 红米K30pro变焦版更多使用感受和评价&#x…

MySQL中的锁(表锁、行锁)

锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所在有数据库必须解决的一个问题&am…

Vue中的Ajax 配置代理 slot插槽

4.1.Vue脚手架配置代理 本案例需要下载axios库npm install axios 配置参考文档Vue-Cli devServer.proxy vue.config.js 是一个可选的配置文件&#xff0c;如果项目的 (和 package.json 同级的) 根目录中存在这个文件&#xff0c;那么它会被 vue/cli-service 自动加载。你也可…

Linux 中创建快捷方式命令 ln 详解

前言 在 Linux / Unix 系统中&#xff0c;链接文件提供了一种方便的方式来访问已存在的文件&#xff0c;或者共享一个文件给多个用户。Linux 命令 ln 可以创建两种类型的链接文件&#xff1a;硬链接和符号链接。本文将全面详细讲解 Linux 命令 ln 的使用方法&#xff0c;并且提…

HDU 2154跳舞毯

最简单的dp题目 dp[i][j] j1表示第i步在红色的步数 j2表示第i步在黄色的步数 j3表示第i步在蓝色的步数 对于每次跳到红色的状态有:第i-1必须是在蓝色和黄色的地板上 所以dp[i][1]dp[i-1][2]dp[i-1][3]&#xff1b; 第i步黄色也有dp[i][2]dp[i-1][1]dp[i-1][3]&#xff1…

Homekit智能家居创意DIY之智能吸顶灯

买灯要看什么因素 好灯具的灯光可以说是家居的“魔术师”&#xff0c;除了实用的照明功能外&#xff0c;对细节的把控也非常到位。那么该如何选到一款各方面合适的灯呢&#xff1f; 照度 可以简单理解为清晰度&#xff0c;复杂点套公式来说照度光通量&#xff08;亮度&#…

hd 2154 跳舞毯

跳舞毯 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3247 Accepted Submission(s): 1532 Problem Description 由于长期缺乏运动&#xff0c;小黑发现自己的身材臃肿了许多&#xff0c;于是他想健身&…

亚马逊美国站TEMU跳舞毯CPC认证检测项目分析

跳舞毯上架亚马逊美国站或者出口美国需要提交CPC认证&#xff0c;才能进入美国市场&#xff0c;由CPSC 认可的实验室出具的检测报告&#xff0c;确认每件商品均已过检测&#xff0c;符合上述适用要求。 亚马逊美国站上传新产品&#xff0c; 很多时候都是需要类目审核的 &#x…