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://www.rhkb.cn/news/374444.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java设计模式(十五)命令模式(Command Pattern)

1、模式介绍&#xff1a; 命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;其主要目的是将请求封装成一个对象&#xff0c;从而允许使用不同的请求、队列或者日志来参数化其他对象。这种模式使得命令的请求者和实现者解耦。 2、应用场景&…

服务启动何时触发 Nacos 的注册流程?

前言&#xff1a; 前面的系列文章让我们对 Nacos 有了一个基本了解&#xff0c;并知道了如何去试用 Nacos 作为注册中心和配置中心&#xff0c;本篇我们将从源码层面去分析 Nacos 的服务注册流程。 Nacos 系列文章传送门&#xff1a; Nacos 初步认识和 Nacos 部署细节 Naco…

C++基础学习笔记

1.命名空间(namespace) 1.什么是命名空间&命名空间的作用 1.在C/C中&#xff0c;变量、函数、类都是大量存在的&#xff0c;这些变量等的名称将都存在于全局作用域中&#xff0c;就会导致很多的命名冲突等。使用命名空间的目的就是对标识符的名称进行本地化&#xff0c;以…

短视频矩阵系统全解析:让获客变得更简单

随着数字媒体的迅猛发展&#xff0c;短视频已成为人们生活中不可或缺的一部分。对于企业而言&#xff0c;如何有效利用短视频平台吸引目标用户&#xff0c;实现高效获客&#xff0c;成为了一个亟待解决的问题。本文将全面解析短视频矩阵系统&#xff0c;带您领略其独特魅力&…

广度优先(BFS)

先看一道简单的题&#xff0c;迷宫问题&#xff1a; 洛谷P1746 离开中山路&#xff1a;P1746 离开中山路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> #include<cstring> #include<queue> #include <utility> #define N 1002 …

深度学习的数学PDF

链接: https://pan.baidu.com/s/1_jScZ7dcyAWGqbrad6bbCQ?pwd9gj9 提取码: 9gj9 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦

最简单的vue3组件之间传值

localStorage 是 HTML5 引入的一个 Web Storage API 的一部分&#xff0c;它允许网页在用户的浏览器上存储数据。localStorage 提供了一种持久化的本地存储方案&#xff0c;数据不会因为浏览器关闭而丢失&#xff0c;除非用户或脚本显式地删除它们。 localStorage 是一种非常实…

VSCode神仙插件——通义灵码 (AI编程助手)

1、安装&登录插件 安装时,右下角会有弹窗,让你登录该软件 同意登录后,会跳转浏览器页面 VSCode右下角出现如下图标即登录成功 2、使用 (1)点击左侧栏中的如下图标,打开通义灵码,可以进行智能问答 (2) 选中代码,右键 但是,上述所有的操作会在左侧问答栏中提供答案,并无法直…

认识并理解webSocket

今天逛牛客&#xff0c;看到有大佬分享说前端面试的时候遇到了关于webSocket的问题&#xff0c;一看自己都没见过这个知识点&#xff0c;赶紧学习一下&#xff0c;在此记录&#xff01; WebSocket 是一种网络通信协议&#xff0c;提供了全双工通信渠道&#xff0c;即客户端和服…

31. 1049. 最后一块石头的重量 II, 494.目标和,474.一和零

class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum 0;for(int stone : stones) sum stone;int bagSize sum /2;vector<int> dp(bagSize 1, 0);for(int i 0; i < stones.size(); i){ //遍历物品for(int j bagSize; j >…

LLMs的基本组成:向量、Tokens和嵌入

编者按&#xff1a;随着人工智能技术的不断发展&#xff0c;大模型&#xff08;语言、视觉&#xff0c;或多模态模型&#xff09;已成为当今AI应用的核心组成部分。这些模型具有处理和理解自然语言等模态输入的能力&#xff0c;推动了诸如聊天机器人、智能助手、自动文本生成等…

Android初学者书籍推荐

书单 1.《Android应用开发项目式教程》&#xff0c;机械工业出版社&#xff0c;2024年出版2.《第一行代码Android》第二版3.《第一行代码Android》第三版4.《疯狂Android讲义》第四版5.《Android移动应用基础教程&#xff08;Android Studio 第2版&#xff09;》 从学安卓到用安…

Node.js如何在Windows安装?

文章目录 主要特点&#xff1a;使用场景&#xff1a;安装方法验证是否安装成功 Node.js 是一个开源、跨平台的JavaScript运行环境&#xff0c;由Ryan Dahl于2009年创建。它允许开发者在服务器端运行JavaScript代码。Node.js 基于Chrome V8 JavaScript引擎构建&#xff0c;其设计…

项目/代码规范与Apifox介绍使用

目录 目录 一、项目规范&#xff1a; &#xff08;一&#xff09;项目结构&#xff1a; &#xff08;二&#xff09;传送的数据对象体 二、代码规范&#xff1a; &#xff08;一&#xff09;数据库命名规范&#xff1a; &#xff08;二&#xff09;注释规范&#xff1a; …

关于CANNM PassiveMode

Passive Mode的要求 根据上图CANNM的规范可知&#xff1a; 处于Passive Mode的网络节点只能接收网络管理PDU&#xff0c;不能发送网络管理PDU。Passive Mode由CanNmPassiveModeEnable参数静态配置。如果一个ECU包含多个节点&#xff0c;那么所有的节点要么都是Passive Mode要么…

GD32F303之CAN通信

1、CAN时钟 GD32F303主时钟频率最大是120Mhz,然后APB1时钟最大是60Mhz,APB2时钟最大是120Mhz,CAN挂载在APB1总线上面 所以一般CAN的时钟频率是60Mhz,这个频率和后面配置波特率有关 2、GD32F303时钟配置 首先我们知道芯片有几个时钟 HXTAL&#xff1a;高速外部时钟&#xff1…

elementui实现复杂表单的实践

简介 文章主要讲述在vue3项目中使用elementui框架实现复杂表单的方式。表单中涉及动态组件的生成、文件上传和富文本编辑器的使用&#xff0c;只会将在实现过程中较复杂的部分进行分享&#xff0c;然后提供一份完整的前端代码。 表单效果演示 基础信息 spu属性 sku详情 关键…

曝宝马汽车门店亏损严重价格战带来的伤害太大了

今年以来不仅餐饮行业难,就连一些车企都陷入困境当中,多家车企选择打价 格战。只不过日前的时候媒体爆料称,宝马汽车门店因为打价格战,最终亏损严 重,为了避免亏损再度出现,因此宝马7月将会开始降量保价。文章来源于&#xff1a;股城网www.gucheng.com 实际上,进入2024年…

如何在 C 语言中进行选择排序?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&#xff0c;看过的人都说好。 文章目…

B站大课堂-自动化精品视频(个人存档)

基础知识 工业通信协议 Modbus 施耐德研发&#xff0c;有基于以太网的 ModbusTCP 协议和使用 485/232 串口通信的 ModbusRTU/ASCII。 Modbus 协议面世较早、协议简洁高效、商用免费、功能灵活、实现简单&#xff0c;是目前应用最广泛的现场总线协议。 我的笔记里边有一些推荐…