21.Linux 线程库的使用与封装

embedded/2025/3/13 16:26:12/

在linux内核中并没有线程的概念,只有轻量级进程LWP的概念,linux下的线程都是是由LWP进行模拟实现的。因此linux操作系统中不会提供线程的相关接口,只会提供轻量级线程的接口(如vfork,clone等)。但是在我们的学习过程中实际上学到的都是线程的概念,故而linux的设计者在用户和操作系统之间将轻量级进程的的系统调用进行了封装成库并提供给了用户,这样的线程我们称之为用户级线程库

接下来我们介绍一下linux系统中线程库的使用与封装。

一、线程库的使用

1.1 POSIX线程库

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

1.2 线程库的接口

1.2.1 线程创建

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg);
//功能:创建一个新的线程
//参数://thread:输出型参数,用以返回线程ID//attr:设置线程的属性,attr为NULL表示使⽤默认属性//start_routine:是个函数地址,即线程启动后要执⾏的函数,它的返回值也可以是任意类型(整型,字符型,对象)//arg:传给线程启动函数的参数,可以是任意类型(整型,字符型,对象……)
//返回值:成功返回0,失败返回错误码

示例:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;void* run(void* arg)
{while(true){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}
}int main()
{pthread_t thread1;pthread_create(&thread1,nullptr,run,(void*)"new thread1");pthread_t thread2;pthread_create(&thread2,nullptr,run,(void*)"new thread2");while(true){cout<<"main thread"<<endl;sleep(1);}return 0;
}

注意:
• 新线程和主线程的运行顺序是不确定的。
• 多线程的调度时间是基于对进程的时间片进行瓜分。
• 多个线程向同一个文件(这里是显示器)进行写入时,在不加保护的情况下是会发生重入的,也就会产生数据不一致问题。
• 线程是共享进程地址空间的。
• 线程出现异常会导致当前进程的其他线程全部崩溃。

1.2.2 线程终止

和进程一样,线程创建之后也是要被等待和回收的!

如果需要只终止某个线程而不终止整个进程的话,有三种方法:
• 线程执行函数return。这种方法对主线程不适用,main函数return相当于调用exit。
• 线程可以调用pthread_exit终止自己。

void pthread_exit(void *value_ptr);
//功能:线程终⽌
//参数://value_ptr:线程退出时的返回值。

需要注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配。因为当其它线程得到这个返回指针时线程函数已经退出了。

• 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

int pthread_cancel(pthread_t thread);
//功能:取消一个执⾏中的线程
//参数://thread:要取消的线程ID
//返回值:成功返回0,失败返回错误码

1.2.3 线程等待

为什么需要线程等待?
因为已经退出的线程其空间没有被释放,仍然在进程的地址空间内。而创建新的线程不会复用刚才退出线程的地址空间。

int pthread_join(pthread_t thread, void **value_ptr);
//功能:等待线程结束
//参数://thread:线程ID//value_ptr:用以保存线程的返回值的地址
//返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。

thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的:
• 如果thread线程通过return返回,那么value_ptr所指向的单元里存放的是thread线程函数的返回值。
• 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED((void*)-1)。
• 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
• 如果对thread线程的终止状态不感兴趣,则可以传NULL给value_ptr参数。

在这里插入图片描述
示例:

#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;void* run1(void* arg)
{int count=5;while(count--){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}pthread_exit((void*)100);cout<<"退出成功!"<<endl;}void* run2(void* arg)
{while(true){cout<<"new thread :"<<(char* )arg<<endl;sleep(1);}
}
int main()
{//创建线程1,线程2pthread_t thread1;pthread_create(&thread1,nullptr,run1,(void*)"new thread1");pthread_t thread2;pthread_create(&thread2,nullptr,run2,(void*)"new thread2");sleep(3);//取消线程2pthread_cancel(thread2);cout<<"取消成功!"<<endl;//线程等待void *ret1,*ret2;pthread_join(thread1,&ret1);pthread_join(thread2,&ret2);cout<<"等待成功!"<<endl;cout<<"ret1 :"<<(long long)ret1<<";ret2 :"<<(long long)ret2<<endl;return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread 
new thread :new thread1
new thread :new thread2
new thread :new thread1
new thread :new thread2
new thread :new thread1
new thread :new thread2
取消成功!
new thread :new thread1
new thread :new thread1
等待成功!
ret1 :100;ret2 :-1

在多执行流的情况下,主执行流往往是最后退出的!

1.2.4 线程分离

默认情况下,新创建的线程是joinable的,线程退出后需要对其进型pthread_join操作,否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值的话join就是一种负担。这个时候我们可以告诉系统,当线程退出时,自动释放线程资源。

int pthread_detach(pthread_t thread);
//功能:进行线程分离
//参数://thread:被分离的线程ID
//返回值:成功返回0;失败返回错误码

可以是线程组内其他线程对目标线程进型分离,也可以是线程自己分离。joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *thread_run(void *arg)
{printf("%s\n", (char *)arg);return NULL;
}
int main(void)
{//创建线程pthread_t tid;pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") ;//分离线程pthread_detach(tid);int ret = 0;sleep(1); if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");ret = 0;}else{printf("pthread wait failed\n");ret = 1;}return ret;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread 
thread1 run...
pthread wait failed

二、线程id与进程空间布局

在线程创建、终止、等待时我们都有注意到pthread_t thread(线程id)这个参数的存在,这个id表示的是什么意思呢?

#include<iostream>
#include<pthread.h>
using namespace std;void* run(void* args)
{while(true);return nullptr;    
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,run,(void*)"newthread");printf("0x%lx\n",tid);return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread 
0x7fae58e006c0

我们可以看到这个id的值是一个地址,那这个地址是什么地址呀?

通过ldd查看我们所形成的可执行程序是依赖pthread库的,这个库在程序运行时会加载到内存中,进而映射到进程地址空间里被所有的线程所共享。我们又知道linux系统中只有轻量级进程,线程是使用轻量级进程模拟实现的。但是用户想要查看当前线程的属性怎么办呢?
linux内核中是存在这些属性的,但是Linux中不存在线程呀!这需要内核层与用户层进行解耦啊!故而线程的相关属性也是由pthread库进行维护的,用户可以通过pthread库来查看线程的属性。

所以这个地址就是该线程的属性所对应的进程地址空间中的地址!

我们可以使用下面这个函数获取当前线程的id:

pthread_t pthread_self(void);

在这里插入图片描述
上图就是进程地址空间中的tcb了。
struct pthread
这个结构里面一定封装了LWP。
线程栈
进程地址空间中的栈是主线程的栈,新线程的栈是通过动态申请创建的。
线程局部存储
理论上来说全部变量是所有线程所共享的,即他们所访问的全局变量的地址是相同的!但是在声明全局变量是加上__thread进行修饰(修饰的只能是内置类型)就表示给每个线程都来一份,即线程的局部性存储。

#include<iostream>
#include<pthread.h>
using namespace std;__thread int count=100;void* run(void* args)
{printf("new thread &count : %p\n",&count);return nullptr;    
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,run,(void*)"newthread");pthread_join(tid,nullptr);printf("main thread &count : %p\n",&count);return 0;
}
caryon@VM-24-10-ubuntu:~/linux/thread$ ./thread 
new thread &count : 0x71e23b2006bc
main thread &count : 0x71e23ba8e4fc

三、线程的封装

#pragma once#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>using func_t = std::function<void()>;
static int number = 1;
enum class TSTATUS
{NEW,RUNNING,STOP
};class Thread
{
private:// 成员方法!static void *Routine(void *args){Thread *t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func();return nullptr;}void EnableDetach() { _joinable = false; }bool IsJoinable() { return _joinable; }
public:Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread-" + std::to_string(number++);_pid = getpid();}bool Start(){if (_status != TSTATUS::RUNNING){int n = ::pthread_create(&_tid, nullptr, Routine, this); if (n != 0)return false;return true;}return false;}bool Stop(){if (_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Join(){if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}void Detach(){EnableDetach();pthread_detach(_tid);}~Thread(){}
private:std::string _name;pthread_t _tid;pid_t _pid;bool _joinable; // 是否是分离的,默认不是func_t _func;TSTATUS _status;
};
文章来源:https://blog.csdn.net/2301_80258336/article/details/146165294
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ppmy.cn/embedded/172298.html

相关文章

天润融通走进蔚来汽车,探索AI在厂店一体化中的应用

存量时代&#xff0c;汽车行业正在积极探索创新发展模式。企业对于提升客户体验、优化运营管理的需求愈发迫切&#xff0c;AI技术正成为车企提高竞争力的重要锚点。 2月28日&#xff0c;天润融通带领众多汽车行业客户走进蔚来汽车&#xff0c;实地参观蔚来汽车牛屋中心&#x…

【数据结构】第六章:图

本篇笔记课程来源&#xff1a;王道计算机考研 数据结构 【数据结构】第六章&#xff1a;图 一、图的定义1. 基本概念2. 特殊的图 二、图的存储结构1. 邻接矩阵2. 邻接表3. 十字链表4. 邻接多重表5. 四种存储结构对比 三、图的基本操作四、图的遍历算法1. 广度优先遍历2. 深度优…

基于单片机的智慧农业大棚系统(论文+源码)

1系统整体设计 经过上述的方案分析&#xff0c;采用STM32单片机为核心&#xff0c;结合串口通信模块&#xff0c;温湿度传感器&#xff0c;光照传感器&#xff0c;土壤湿度传感器&#xff0c;LED灯等硬件设备来构成整个控制系统。系统可以实现环境的温湿度检测&#xff0c;土壤…

python编写的一个打砖块小游戏

游戏介绍 打砖块是一款经典的街机游戏&#xff0c;玩家控制底部的挡板&#xff0c;使球反弹以击碎上方的砖块。当球击中砖块时&#xff0c;砖块消失&#xff0c;球反弹&#xff1b;若球碰到挡板&#xff0c;则改变方向继续运动&#xff1b;若球掉出屏幕底部&#xff0c;玩家失…

软件测试 - 性能测试 (概念)(并发数、吞吐量、响应时间、TPS、QPS、基准测试、并发测试、负载测试、压力测试、稳定性测试)

一、性能测试 目标&#xff1a;能够对个人编写的项目进行接口的性能测试。 一般是功能测试完成之后&#xff0c;最后做性能测试。性能测试是一个很大的范围&#xff0c;在学习过程中很难直观感受到性能。 以购物软件为例&#xff1a; 1&#xff09;购物过程中⻚⾯突然⽆法打开…

机器学习的下一个前沿是因果推理吗?——探索机器学习的未来方向!

机器学习的进化&#xff1a;从预测到因果推理 机器学习凭借强大的预测能力&#xff0c;已经彻底改变了多个行业。然而&#xff0c;要实现真正的突破&#xff0c;机器学习还需要克服实践和计算上的挑战&#xff0c;特别是在因果推理方面的应用。未来&#xff0c;因果推理或许将…

qt 多进程使用共享内存 ,加速数据读写,进程间通信 共享内存

Summary: 项目中我们有时需要使用共享内存共享数据&#xff0c;这样&#xff0c;数据不用进程IO读写&#xff0c;加进数据加载和落地&#xff1b; 程序退出时&#xff0c;再保存到本地&#xff1b;速度提升数十倍&#xff1b; Part1:QSharedMemory Windows平台下进程间通信…

纯前端全文检索的两种实现方案:ElasticLunr.js 和 libsearch

纯前端全文检索的两种实现方案&#xff1a;ElasticLunr.js 和 libsearch 在前端开发中&#xff0c;实现全文检索功能可以显著提升用户体验&#xff0c;尤其是在处理大量文本数据时。本文将介绍两种流行的纯前端全文检索方案&#xff1a;ElasticLunr.js 和 libsearch。这两种方…