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

news2024/9/9 4:36:00

目录

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才需要stop
            if(_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;//ID
        bool _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.coloradmin.cn/o/1918303.html

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

相关文章

四川赤橙宏海商务信息咨询有限公司抖音电商服务靠谱吗?

在数字化浪潮席卷全球的今天&#xff0c;电商行业蓬勃发展&#xff0c;各种新兴电商平台层出不穷。其中&#xff0c;抖音电商以其独特的社交属性和庞大的用户基础&#xff0c;迅速崛起为行业新星。四川赤橙宏海商务信息咨询有限公司&#xff0c;作为专注于抖音电商服务的佼佼者…

自动编码器(Autoencoders)

在“深度学习”系列中&#xff0c;我们不会看到如何使用深度学习来解决端到端的复杂问题&#xff0c;就像我们在《A.I. Odyssey》中所做的那样。我们更愿意看看不同的技术&#xff0c;以及一些示例和应用程序。 1、引言 ① 什么是自动编码器&#xff08;AutoEncoder&#xff…

【js/ts】js/ts高精度加减乘除函数

加法 /*** 高精度加法函数&#xff0c;处理字符串或数字输入&#xff0c;去除尾部多余的零* param {string|number} a - 被加数* param {string|number} b - 加数* returns {string} - 计算结果&#xff0c;去除尾部多余的零* throws {Error} - 如果输入不是有效的数字&#x…

油罐车的罐体结构介绍

油罐车的罐体一般用优质低碳钢板制成&#xff0c;罐内被隔板分为前、后两部分&#xff0c;相互隔离。每个舱内部均配备一道防波板&#xff0c;以加强罐体的稳定性并减缓行驶中油料对罐体的冲击。其车身由车架、车厢等组成&#xff0c;车架是整个油罐车的骨架&#xff0c;承载着…

项目收获总结--本地缓存方案选型及使用缓存的坑

本地缓存方案选型及使用缓存的坑 一、摘要二、本地缓存三、本地缓存实现方案3.1 自己编程实现一个缓存3.2 基于 Guava Cache 实现本地缓存3.3 基于 Caffeine 实现本地缓存3.4 基于 Encache 实现本地缓存3.5 小结 四、使用缓存的坑4.1 缓存穿透4.2 缓存击穿4.3 缓存雪崩4.4 数据…

浏览器输入URL后的过程

总体流程&#xff1a; 1. 用户输入URL并按下回车 当用户在浏览器的地址栏中输入一个 URL 并按下回车&#xff0c;浏览器开始解析用户输入并判断这是一个合法的 URL。 2. DNS 解析 缓存查找&#xff1a;浏览器首先查看本地 DNS 缓存中是否有对应的 IP&#xff0c;如果有则直接…

分布式系统—存储ceph部署

目录 一、存储概述 1 单机存储设备 2 单机存储的问题 3 商业存储解决方案 4 分布式存储 二、ceph概述 1 ceph优点 2 ceph架构 3 ceph核心组件 4 OSD存储后端 5 ceph数据存储过程 6 ceph版本发行生命周期 7 ceph集群部署 三、基于 ceph-deploy 部署 Ceph 集群 1 …

多位冒充者曝光!全域外卖官方到底是谁?

当前&#xff0c;全域外卖的热度持续攀升&#xff0c;不少创业者在寻找入局途径的同时&#xff0c;也开始打听与全域外卖官方有关的各种信息。其中&#xff0c;以全域外卖官方是谁为代表的身份类信息更是成为了多个创业者群内的热议问题。 而就目前的讨论情况来看&#xff0c;虽…

hyperworks软件许可优化解决方案

Hyperworks软件介绍 Altair 仿真驱动设计改变了产品开发&#xff0c;使工程师能够减少设计迭代和原型测试。提升科学计算能力扩大了应用分析的机会&#xff0c;使大型设计研究能够在限定的项目时间完成。现在&#xff0c;人工智能在工程领域的应用再次改变了产品开发。基于物理…

晋升业内新宠儿,MoE模型给了AI行业两条关键出路

文 | 智能相对论 作者 | 陈泊丞 今年以来&#xff0c;MoE模型成了AI行业的新宠儿。 一方面&#xff0c;越来越多的厂商在自家的闭源模型上采用了MoE架构。在海外&#xff0c;OpenAI的GPT-4、谷歌的Gemini、Mistral AI的Mistral、xAI的Grok-1等主流大模型都采用了MoE架构。 …

回收站清空了怎么恢复回来?8个数据恢复方法汇总分享!

在日常工作中&#xff0c;我们常常会遇到一个令人头痛的问题&#xff1a;回收站清空了怎么恢复回来&#xff1f;这种情况其实比想象中更常见。有时在整理桌面时可能会不小心彻底清理文件&#xff0c;或者误开启了回收站的自动清理功能&#xff0c;甚至可能因为病毒或bug而意外丢…

dxf数据结构

DXF&#xff08;Drawing Exchange Format&#xff0c;绘图交换格式&#xff09;是Autodesk公司开发的一种CAD&#xff08;计算机辅助设计&#xff09;文件格式&#xff0c;用于实现AutoCAD与其他软件之间的CAD数据交换。DXF格式文件是一种开放的矢量数据格式&#xff0c;具有多…

34 超级数据查看器 关联图片

超级数据查看器app&#xff08;excel工具&#xff0c;数据库软件&#xff0c;表格app&#xff09; 关联图片讲解 点击 打开该讲的视频 点击访问app下载页面 豌豆荚 下载地址 大家好&#xff0c;今天我们讲一下超级数据查看器的关联图片功能 这个功能能让表中的每一条信息&…

【区块链 + 智慧政务】城市大脑数据监管平台 | FISCO BCOS应用案例

为了实现城市的智能化管理和服务&#xff0c;智慧城市建设需要将大量的公共数据和社会数据汇集到城市大脑数据中台。 通过汇聚各种类型的数据&#xff0c;城市管理者可以实时了解城市的运行状态和居民的需求&#xff0c;从而更好地进行城市规划、 资源分配和公共服务的提供。 …

Figma中文网?比Figma更懂你的神秘网站!

Figma奠定了在线UI设计工具的基本形式&#xff0c;许多国内设计师都在使用Figma。在本文中&#xff0c;我们将解密国内大型设计师使用的Figma灵魂合作伙伴&#xff0c;被称为Figma中文网络的即时设计资源社区。Figma中文网络UI设计工具的魅力是什么&#xff1f;让我们一起看看吧…

uniapp小程序上传文件webapi后端项目asp.net

需求 小程序需要上传用户相册图片或拍摄的照片到后端服务器 uniapp官方处理小程序文件方法 选择文件方法&#xff1a;uni.chooseMedia uni-app官网uni-app,uniCloud,serverless,uni.chooseVideo(OBJECT),chooseVideo HarmonyOS 兼容性,uni.chooseMedia(OBJECT),uni.saveVid…

EasyExcel批量读取Excel文件数据导入到MySQL表中

1、EasyExcel简介 官网&#xff1a;EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 官网 2、代码实战 首先引入jar包 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</v…

Facebook的未来蓝图:从元宇宙到虚拟现实的跨越

随着科技的不断演进和社会的数字化转型&#xff0c;虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;作为下一代计算平台正逐渐走进人们的视野。作为全球领先的科技公司之一&#xff0c;Facebook正在积极探索并推动这一领域的发展&#xff0c;以实现其…

【linux】服务器卸载cuda

【linux】服务器卸载cuda 文章目录 【linux】服务器卸载cuda1、查找已安装的 CUDA 包&#xff1a;2、卸载 CUDA&#xff1a;3、删除残留文件4、更新系统的包索引&#xff1a;5、检查是否卸载干净&#xff1a; 1、查找已安装的 CUDA 包&#xff1a; dpkg -l | grep cuda2、卸载…