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才需要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;
}

运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

8.9分王者“水刊”!1区IEEE-Trans,国人主编坐镇!发文量2倍增长,扩刊趋势明显!

关注GZH【欧亚科睿学术】&#xff0c;第一时间了解最新期刊动态&#xff01; 本期&#xff0c;小编给大家推荐的是一本IEEE旗下王者“水刊”。该期刊目前处于扩刊状态&#xff0c;接收跨学科领域&#xff0c;领域认可度高&#xff0c;还可选择非OA模式无需版面费&#xff0c;是…

css看见彩虹,吃定彩虹

css彩虹 .f111 {width: 200px;height: 200px;border-radius: 50%;box-shadow: 0 0 0 5px inset red, 0 0 0 10px inset orange, 0 0 0 15px inset yellow, 0 0 0 20px inset lime, 0 0 0 25px inset aqua, 0 0 0 30px inset blue, 0 0 0 35px inset magenta;clip-path: polygo…

力扣爆刷第163天之TOP100五连刷81-85(回文链表、路径和、最长重复子数组)

力扣爆刷第163天之TOP100五连刷81-85&#xff08;回文链表、路径和、最长重复子数组&#xff09; 文章目录 力扣爆刷第163天之TOP100五连刷81-85&#xff08;回文链表、路径和、最长重复子数组&#xff09;一、234. 回文链表二、112. 路径总和三、169. 多数元素四、662. 二叉树…

盲人出行好帮手:蝙蝠避障让走路变简单

在一片无光的世界里&#xff0c;每一步都承载着探索与勇气。我是众多盲人中的一员&#xff0c;每天的出行不仅是从&#xff21;点到&#xff22;点的物理移动&#xff0c;更是一场心灵的征程。我的世界&#xff0c;虽然被剥夺了视觉的馈赠&#xff0c;却因科技的力量而变得宽广…

保护企业数据资产的策略与实践:数据安全治理技术之实战篇!

在上篇文章中&#xff0c;我们深入讨论了数据安全治理技术的前期准备工作&#xff0c;包括从建立数据安全运维体系、敏感数据识别、数据的分类与分级到身份认等方面的详细规划和设计。这些准备工作是实现数据安全治理的基础&#xff0c;它们为企业建立起一套系统化、标准化的数…

2.电容(常见元器件及电路基础知识)

一.电容种类 1.固态电容 这种一般价格贵一些&#xff0c;ESR,ESL比较低,之前项目400W电源用的就是这个&#xff0c;温升能够很好的控制 2.铝电解电容 这种一般很便宜&#xff0c;ESR,ESL相对大一些&#xff0c;一般发热量比较大&#xff0c;烫手。 这种一般比上一个贵一点&am…

[leetcode]maximum-binary-tree 最大二叉树

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:TreeNode* constructMaximumBinaryTree(vector<int>& nums) {return construct(nums, 0, nums.size() - 1);}TreeNode* construct(const vector<int>& nums, int left, int right) {if …

快速掌握 ==== js 正则表达式

git 地址 https://gitee.com/childe-jia/reg-test.git 背景 在日常开发中&#xff0c;我们经常会遇到使用正则表达式的场景&#xff0c;比如一些常见的表单校验&#xff0c;会让你匹配用户输入的手机号或者身份信息是否规范&#xff0c;这就可以用正则表达式去匹配。相信大多数…

CSS3实现彩色变形爱心动画【附源码】

随着前端技术的发展&#xff0c;CSS3 为我们提供了丰富的动画效果&#xff0c;使得网页设计更加生动和有趣。今天&#xff0c;我们将探讨如何使用 CSS3 实现一个彩色变形爱心加载动画特效。这种动画不仅美观&#xff0c;而且可以应用于各种网页元素&#xff0c;比如加载指示器或…

收集路径下的html并写到根目录index.html

我们可以用简单的脚本生成一个简单的导航页。 用于查看当前路径下所有的html。 #!/bin/bash index_root"/root/test/" cd ${index_root} echo "" > index.htmlecho "<html><head><title>Test Report Index</title></…

VBA 批量发送邮件

1. 布局 2. 代码 前期绑定的话&#xff0c;需要勾选 Microsoft Outlook 16.0 Object Library Option ExplicitConst SEND_Y As String "Yes" Const SEND_N As String "No" Const SEND_SELECT_ALL As String "Select All" Const SEND_CANCEL…

VSCode升级后不能打开在MacOS系统上

VSCode 在MacOS无法打开 版本 VSCode version: 1.91.0 (x64) 错误信息&#xff1a; MacBook-Pro ~ % /Users/mac/Downloads/FirefoxDownloads/Visual\ Studio\ Code.app/Contents/MacOS/Electron ; exit; [0710/142747.971951:ERROR:crash_report_database_mac.mm(753)] op…

网络建设与运维23国赛网络运维正式赛题解析

竞赛环境请看主页&#xff01; 23国赛网络运维 任务描述&#xff1a;某集团公司在更新设备后&#xff0c;路由之间无法正常通信&#xff0c;请修 复网络达到正常通信。 &#xff08;1&#xff09; 请在server1“管理员”下拉菜单中选择“镜像”选项卡&#xff0c;点 击 “创…

浪潮天启防火墙TQ2000远程配置方法SSL-xxx、L2xx 配置方法

前言 本次设置只针对配置VXX&#xff0c;其他防火墙配置不涉及。建议把防火墙内外网都调通后再进行Vxx配置。 其他配置可参考&#xff1a;浪潮天启防火墙配置手册 配置SSLVxx 在外网端口开启SSLVxx信息 开启SSLVxx功能 1、勾选 “启用SSL-Vxx” 2、设置登录端口号&#xff0…

扫描全能王AIGC“黑科技”亮相WAIC,《人民日报》、央视、新华社同时“点赞”

2024年世界人工智能大会&#xff08;WAIC&#xff09;于近期圆满闭幕。今年&#xff0c;合合信息旗下扫描全能王展台成为大会的“网红”&#xff0c;以AI古籍修复为代表的体验项目不仅赢得了专业观众的赞誉&#xff0c;也获得了包括CCTV-4、CCTV-13、《人民日报》、新华社、解放…

好用的IP反查接口(2)

IP-地理信息查询接口-本地化 参考&#xff1a; 通过Ip查询对应地址,Ip2location全球IP地址网段-CSDN博客 因为在线接口有限制&#xff08;毕竟别人提供服务&#xff0c;服务器&#xff0c;数据维护&#xff0c;域名啥的都要费用&#xff09;&#xff0c; 所以本地化服务的需…

【K8s】专题七(1):Kubernetes 服务发现之 Service

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本介绍 二、工作原理 三、对象类型 四、资源清单&#xff08;示例&#xff09; 五…

Java---SpringBoot详解一

人性本善亦本恶&#xff0c; 喜怒哀乐显真情。 寒冬暖夏皆有道&#xff0c; 善恶终归一念间。 善念慈悲天下广&#xff0c; 恶行自缚梦难安。 人心如镜自省照&#xff0c; 善恶分明照乾坤。 目录 一&#xff0c;入门程序 ①&#xff0c;创建springboot工程&#…

【Qt】QTabWidget的tab页隐藏问题

在Qt中&#xff0c;使用 ​ui->tab1->setHidden(true);​ 来隐藏一个 ​QTabWidget​ 的特定标签页可能不会达到预期的效果&#xff0c;因为 ​setHidden(true)​ 是用于隐藏整个 ​QWidget​ 的&#xff0c;而不是隐藏 ​QTabWidget​ 中的一个标签页。 要隐藏 ​QTabW…

Python高级(三)_正则表达式

Python高级-正则表达式 第三章 正则表达式 在开发中会有大量的字符串处理工作,其中经常会涉及到字符串格式的校验。 1、正则表达式概述 正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、…