Linux--线程ID封装管理原生线程

目录

1.线程的tid(本质是线程属性集合的起始虚拟地址)

1.1pthread库中线程的tid是什么?

1.2理解库

1.3phtread库中做了什么?

1.4线程的tid,和内核中的lwp

1.5线程的局部存储

2.封装管理原生线程库 


1.线程的tid(本质是线程属性集合的起始虚拟地址)

1.1pthread库中线程的tid是什么?

首先我们写一个程序获取线程id:

        我们可以看到给用户提供的线程ID,不是内核中的LWP,而是pthread库中维护的一个唯一值,库内部也要承担对线程的管理。

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>void*threadrun(void *args)
{std::string name =static_cast<const char*>(args);while(true){std::cout<<name<<"is running,tid: "<<pthread_self()<<std::endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");std::cout<<"new thread tid: "<<tid<<std::endl;pthread_join(tid,nullptr);return 0;
}

为了方便查看,我们将tid转成16进制:

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>std::string ToHex(pthread_t tid)
{char id[128];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
void*threadrun(void *args)
{std::string name =static_cast<const char*>(args);while(true){std::string id = ToHex(pthread_self());std::cout<<name<<"is running,tid: "<<id<<std::endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");std::cout<<"new thread tid: "<<ToHex(tid)<<std::endl;pthread_join(tid,nullptr);return 0;
}

转为16进制我们可以看出来tid实际上是一个地址


1.2理解库

        首先我们要知道pthread库实际上是Linux中的一个文件,这个文件我们称它为:pthread库。这个库默认我们没有运行多线程时,他是在磁盘上的,他是一个动态库

        我们生成的可执行程序在没运行的时候,当然也是在磁盘中的。当运行的时候,我们的可执行程序要加载到内存中,程序要变成一个进程的时候,它的PCB和内核数据结构要被创建出来,通过页表映射到可执行程序的代码和数据  

        多线程在启动之前首先要是一个进程,然后调用接口动态的创建多线程。调用接口创建线程,前提是把库加载到内存,映射到进程的地址空间!!!若正文部分调用了创建线程的接口,会跳转到共享区中的库中,库再通过页表映射在内存中,找到实现方法,在库中就把线程创建好了。


1.3phtread库中做了什么?

 我们先不管OS,库是如何做到对线程进行管理呢?(先描述,再组织)

        库中创建一个线程,会为线程申请一个内存块(每创建一个线程申请一个内存块),所有的块都是连续存储的,内存块就是一个大号的结构体,内存块中存在线程在用户及最基本的属性和线程栈,这个栈是线程独立的栈结构。未来我们想要找一个线程的属性直接找到线程管理控制块的地址即可,这个地址就是tid!

举个例子,join是怎么拿到退出结果的?

        在线程执行流结束的时候,库中给线程的内存块是没有被释放的,新线程会把退出结果在线程属性中用(void*)的变量维护起来,只有join了它才会释放。join函数通过线程的地址找到线程的内存块,再将新线程的退出结果拷贝回来。

每个栈是如何独立的

        每个线程都有自己独立的线程控制块。每个线程在创建时都会分配一个独立的栈空间(在自己的控制块中开辟一段合适的内存空间就行了,这个空间大小是可以动态调整的),用于存储线程执行过程中的局部变量、函数调用等信息。这个栈空间是线程私有的,其他线程无法直接访问。新线程的栈在自己的控制块内,主线程的栈是地址空间中的栈。

1.4线程的tid,和内核中的lwp

        首先我们要知道在Linux内核中,线程通常是通过轻量级进程(LWP)来实现的。LWP是内核级别的线程,由操作系统内核直接管理和调度。它们共享同一进程的资源(如内存空间、文件描述符等),但每个LWP都有自己独立的执行上下文和调度状态。他是有自己的系统调用的,比如创建轻量级进程的系统调用:clone,它可以让LWP去执行clone设置的回调函数形成临时变量放在,放在你所指明的栈空间里。所以libpthread.so库就是封装了创建轻量级进程的系统调用:clone

        在用户层我们有libpthread.so库,当用户在用户态通过pthread库等线程库创建线程时会有对应的PCB,最终会被1:1被映射到内核级的LWP上,LWP的表现实际上就是PCB,用来调度和管理轻量级进程。所以线程的概念,只是在库中表现出来的,所以我们把Linux中的线程称之为:用户级线程。所以Linux 线程=pthread库中线程的属性级+LWP,这就是1:1级别用户级线程库的实现


1.5线程的局部存储

示例:新线程(对gval做++)和主线程都在打印全局变量gval的值和地址,一旦全局变量被修改两者是都能看见的,因为都在一个进程内,共享一个地址空间。这种全局变量本身就是多线程之间共享的。

#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>int gval=100;std::string ToHex(pthread_t tid)
{char id[128];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
void*threadrun(void *args)
{std::string name =static_cast<const char*>(args);while(true){std::string id = ToHex(pthread_self());std::cout << name << " is running, tid: " << id << ", gval: " << gval << ", &gval: " << &gval << std::endl;gval++;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");while(true){std::cout << "main thread, gval: " << gval << ", &gval: " << &gval << std::endl;sleep(1);}pthread_join(tid,nullptr);return 0;
}

但如果我想让gval在新线程和主线程中各自私有一份,我们在这里就要使用:__thread

__thread int gval=100;

看效果:此时新线程中和主线程中的gval值不一样地址也不一样,显然它们用的gval不是同一个了。

当使用了__thread关键字后,GCC会在每个线程的上下文中为该变量创建一个独立的实例。这样,每个线程都可以独立地修改其对应的变量实例,而不会影响到其他线程。并且,被__thread修饰的变量会被存储在各自线程的局部存储中,这种存储方式确保了线程间的数据隔离。__thread只在Linux下有效,而且只能修饰内置类型


2.封装管理原生线程库 

C++11中的线程创建,其实就是对原生线程的封装,现在我们也来封装一下原生线程:

Thread.hpp(注释就是实现思路)

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>namespace ThreadMoudle
{// 线程要执行的方法,后面我们随时调整typedef void (*func_t)(const std::string &name); // 函数指针类型//执行函数时,名字带出来,方便打印测试结果class Thread{public://成员方法调用_func执行任务void Excute(){std::cout << _name << " is running" << std::endl;_isrunning = true;//开始回调了,就表示线程跑起来了_func(_name);_isrunning = false;}public://构造Thread(const std::string &name, func_t func):_name(name), _func(func){std::cout << "create " << name << " done" << std::endl;}// 线程的固定历程,新线程都会执行该方法!static void *ThreadRoutine(void *args) {//为了匹配类型,加static属于类而不属于对象,就没有this指针了//this指针从creat函数传递过来Thread *self = static_cast<Thread*>(args); // 获得了当前对象self->Excute();//直接调用成员方法return nullptr;//简单的演示,没有设置返回值}//线程启动bool Start(){//使用标准库中的方法创建进程int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);if(n != 0) return false;return true;}//表示状态std::string Status(){if(_isrunning) return "running";else return "sleep";}void Stop(){//表示有线程在running才需要stopif(_isrunning){::pthread_cancel(_tid);//取消_isrunning = false;//状态变为停止std::cout << _name << " Stop" << std::endl;}}void Join(){//线程退出后等待回收。if(!_isrunning){::pthread_join(_tid, nullptr);std::cout << _name << " Joined" << std::endl;}}//知道是哪个线程std::string Name(){return _name;}~Thread(){}private:std::string _name;//线程名字pthread_t _tid;//IDbool _isrunning;//是否在运行func_t _func; // 线程要执行的回调函数(任务)};
} 

Main.cc

#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"using namespace ThreadMoudle;
void Print(const std::string &name)
{int cnt = 1;while (true){std::cout << name << "is running, cnt: " << cnt++ << std::endl;sleep(1);}
}const int gnum = 10;int main()
{// 我在管理原生线程, 先描述,在组织// 构建线程对象std::vector<Thread> threads;for (int i = 0; i < gnum; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(name, Print);sleep(1);}// 统一启动for (auto &thread : threads){thread.Start();}sleep(10);// 统一结束for (auto &thread : threads){thread.Stop();}// 等待线程等待for (auto &thread : threads){thread.Join();}return 0;
}

运行结果:

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

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

相关文章

odoo视图继承

odoo视图继承 在模型时候&#xff0c;不对视图、菜单等进行修改&#xff0c;原视图和菜单等视图数据仍然可以使用&#xff0c;不需要重新构建 form视图继承案例 model&#xff1a;为对应模型 inherit_id&#xff1a;为继承的视图&#xff0c;ref:为继承视图的id&#xff0…

帝特(DTECH)USB转RS485/422串口线在Ubuntu系统中的安装

因为测试需要&#xff0c;买了一根帝特&#xff08;DTECH&#xff09;USB转RS485/422串口线&#xff0c;今天测试了一下在Ubuntu 22.04系统上的使用。帝特的网站上提供了驱动程序&#xff0c;下载以后发现接口芯片是CP2102&#xff0c;厂商只提供了Linux内核2.6和3.x版本的驱动…

新版FMEA培训未能达到预期效果怎么办?

在制造业的质量管理中&#xff0c;FMEA&#xff08;Failure Mode and Effects Analysis&#xff0c;失效模式与影响分析&#xff09;是一项至关重要的工具&#xff0c;它帮助企业识别和评估产品或过程中潜在的失效模式&#xff0c;以及这些失效模式可能导致的后果。然而&#x…

AIGC技术引领创意设计行业革新,“谁”能成职业发展新引擎?

随着科技的日新月异&#xff0c;生成式人工智能&#xff08;AIGC&#xff09;技术正迅速崛起&#xff0c;成为创意设计领域的一股强大新势力。该技术不仅显著提升了设计师的工作效率&#xff0c;更为他们打开了前所未有的创意空间。在这一波技术浪潮中&#xff0c;Adobe国际认证…

AutoMQ 与蚂蚁数科达成战略合作

近期&#xff0c;AutoMQ 与蚂蚁数科正式签署战略合作协议&#xff0c;将和蚂蚁数科云原生 PaaS 平台 SOFAStack 在产品研发、生态集成、市场合作、技术社区影响力等多方面开展深度合作。 AutoMQ 是业内领先的消息和流存储服务提供商&#xff0c;基于云原生基础设施重新设计了 …

如何整合生成的人工智能?(GenAI)为你未来的工作增加动力

生成人工智能(GenAI)它发展迅速&#xff0c;以前所未有的速度取得了突破。人工智能将继续改变各行各业&#xff0c;预计2023年至2030年的年增长率将达到37.3%。由于一种新的知识工作者现在面临被取代的风险&#xff0c;生成式人工智能的惊人崛起进一步加剧了这种紧迫性。据《未…

虚拟机内安装vue-dev-tools

前言 项目开发调试都需要在Citrix在虚拟机环境下&#xff0c;Citrix内连接不到外网&#xff0c;在这边文章&#xff0c;我将介绍自己在Citrix环境内安装 vue-dev-tools的经验 环境 vue 步骤 1. 下载.crx文件 百度网盘里的 .crx文件的 下载链接 2. 加载.crx文件 打开浏览…

即时通讯平台项目测试(主页面)

http://8.130.98.211:8080/login.html项目访问地址&#xff1a;即时通讯平台http://8.130.98.211:8080/login.html 本篇文章进行项目主页面的测试。 在测试前需要先对待测内容进行分类&#xff0c;按照功能进行分类可以分为&#xff1a;个人信息设置、发送/接收消息、添加好友…

领夹麦克风哪个品牌好,哪个麦克风好,热门无线麦克风品牌推荐

​无线领夹麦克风是现代沟通的重要工具&#xff0c;它不仅提高了语音交流的清晰度&#xff0c;还展现了使用者的专业形象。随着技术发展&#xff0c;这些麦克风已经变得更加轻便、时尚&#xff0c;易于使用。在各种场合&#xff0c;如演讲、教育和网络直播中&#xff0c;当然&a…

请跳至打印机属性的“Adobe PDF设置”页面,取消选择“仅停靠系统字体;不使用文档字体”

场景&#xff1a; 当使用adobe pdf打印时&#xff0c;出现如下提示“请跳至打印机属性的“Adobe PDF设置”页面&#xff0c;取消选择“仅停靠系统字体&#xff1b;不使用文档字体””&#xff0c;该如何解决。 描述 □“仅停靠系统字体&#xff1b;不使用文档字体” 复选本框…

Git常见命令和用法

Git 文件状态 Git 文件 2 种状态: 未跟踪:新文件&#xff0c;从未被 Git 管理过已跟踪:Git 已经知道和管理的文件 常用命令 命令作用注意git -v查看 git 版本git init初始化 git 仓库初始化之后有工作区、暂存区(本地库)、版本库git add 文件标识暂存某个文件文件标识以终…

如何分析软件测试中发现的Bug!

假如你是一名软件测试工程师&#xff0c;每天面对的就是那些“刁钻”的Bug&#xff0c;它们像是隐藏在黑暗中的敌人&#xff0c;时不时跳出来给你一个“惊喜”。那么&#xff0c;如何才能有效地分析和处理这些Bug&#xff0c;让你的测试工作变得高效且有趣呢&#xff1f;今天我…

Python的语言特性

1&#xff0c;python是动态语言 在编译期间就确定变量类型的语言是静态语言 在运行期间才知道变量类型的是动态语言 2&#xff0c;python是强类型语言 不同类型的变量是否允许隐式转换

院内导航:如何用科技破解就医找路难题

自2019年开始“院内导航”被纳入医院智慧服务评估体系以来&#xff0c;到2023年改善就医服务升级的部署&#xff0c;每一步都见证了我国医疗卫生体系向智能化、人性化迈进的坚实步伐。 面对庞大复杂的医院环境与日益增长的就诊需求&#xff0c;如何让患者在茫茫人海中迅速找到就…

Tita的OKR:公司和CEO的OKR案例

为什么为您的组织选择OKR OKR或目标和关键结果是一种协作性的目标设定工具&#xff0c;被团队和组织用来设定具有挑战性的、雄心勃勃的目标和可衡量的结果&#xff0c;OKR是你跟踪进展、创造一致、鼓励参与可衡量的目标的方式。 OKR可以成为创造一个员工能够有目的的工作环境的…

Nifi内置处理器Processor的开发

Nifi-Processor自定义开发的流程 之前说过&#xff0c;大部分的数据处理&#xff0c;我们可以基于ExcuseGroovyScript处理器&#xff0c;编写Groovy脚本去完成&#xff08;或者Jpython&#xff0c;Js脚本等对应的组件&#xff09;&#xff0c;只能说这是基于Nifi平台的使用层面…

迅为3A5000LoongArch架构核心主板支持国产麒麟、统信、以及实时系统翼辉SylixOS

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA&a…

MATLAB数据统计描述和分析

描述性统计就是搜集、整理、加工和分析统计数据&#xff0c; 使之系统化、条理化&#xff0c;以显示出数据资料的趋势、特征和数量关系。它是统计推断的基础&#xff0c;实用性较强&#xff0c;在数学建模的数据描述部分经常使用。 目录 1.频数表和直方图 2 .统计量 3.统计…

基坑安全:自动化监测系统的革新力量

在日新月异的基坑工程领域&#xff0c;基坑安全自动化监测系统犹如一位守护者&#xff0c;以其独特的优势&#xff0c;为工程的安全与质量保驾护航。该系统集先进的测量仪器、计算机技术与现代传感技术于一体&#xff0c;对基坑的围护结构及周边环境进行全方位、高精度的实时监…

00:HAL库的认识

一&#xff1a;HAL库 开发现状&#xff1a; 1&#xff1a;下载 网站&#xff1a; https://www.st.com/zh/embedded-software/stm32cube-mcu-mpu-packages.html 去选择我们的系列 我们使用的是STM32F103C8t6的这个 继续一直向下拉点击这个&#xff1b;之后傻瓜步骤直接可以…