当前位置: 首页 > news >正文

【Linux】从零开始认识多线程 --- 线程ID

在这里插入图片描述

在这个浮躁的时代
只有自律的人才能脱颖而出
-- 《觉醒年代》

1 前言

上一篇文章中讲解了线程控制的基本接口:

  1. 线程创建pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);:

    • pthread_t *thread :输出型参数,线程ID。
    • const pthread_attr_t *attr :线程属性(优先级,上下文…),默认传入nullptr
    • void *(*start_routine) (void *) : 函数指针,线程需要执行的函数地址。
    • void arg:想要传入到线程的信息,可以传入int,string地址或者传入一个类对象的地址。
  2. 线程等待pthread_join(pthread_t thread, void **retval);

    • pthread_t thread:需要进行等待的线程ID
    • void **retval: 获取的返回信息
  3. 线程终止pthread_cancel(pthread_t thread)

    • pthread_t thread:需要进行终止的线程ID

需要深入理解的是线程传参!可以传递任何类型!注意传递堆空间的地址,保持线程独立性!

通过创建的线程我们看到了线程的tid,它和LWP不一样,是类似一个地址的存在,今天我们来详细分析一下线程ID。而且线程是有独立的栈空间的,这个独立栈空间是在哪里的?与线程ID有没有关系?

2 理解线程库与线程ID

我们先来写一个基础的创建线程的程序:

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

我们运行可以看到tid
在这里插入图片描述
也可以通过ps -aL来查看查看运行线程的信息:
在这里插入图片描述
显然tidLWP是不一样的,用户能获取的线程ID不是内核中的LWP,而内核中的LWP其实也不需要给用户呈现,tid是一个库内部自己维护的唯一值,因为库内部需要承担对线程的管理维护。

我们来深入理解库。
首先,库没被加载的时候是在磁盘中的,而线程库是一个动态库,本质上是一个文件。可执行程序也是在磁盘中。刚开始运行的时候,可执行程序会先变成一个进程,加载代码和数据到内存中,并同步创建PCB,页表,地址空间…当CPU调度到这个进程,会运行代码,进而动态创建多线程!

接下来,运行到创建线程的代码的前提是把库加载到进程的地址空间中!动态库也要加载到内存中。进程要使用动态库需要将动态库映射到进程地址空间的共享区中!代码运行到动态库中的代码时会跳转到共享区对应函数,在通过其偏移量映射到内存中库函数实现部分,完成动态库函数的调用!
在这里插入图片描述
LInux内核只会维护轻量级进程,通过LWP(轻量级进程ID)维护,而用户层看到的是线程,需要的是线程的ID,线程的相关属性。上面我们提过线程是线程库维护的!接下来我们来看看线程库内部是如何维护管理的?如果库要管理线程和内核管理进程类似!同样遵循先描述,再组织

  1. 动态库内部会有一个描述线程属性的内存块,每一个线程都会创建这样一个内存块结构,用来描述属性。
  2. 这个内存块内部有线程在用户层面的基本属性,线程的独立栈结构

在这里插入图片描述
也就是在库中创建了描述线程的相关结构体字段属性,因为是连续开辟的,所以管理方法类似数组。未来我们只要找到线程控制的地址即可!pthread_t id就是一个地址!通过这个地址我们就可以访问这个内存块的所有属性!
我们对比一下FILE* fopen()这个接口会返回一个文件指针(而不是一个文件对象),那么这个指针指向的文件是在哪里呢?在C标准库中!返回的也是一个地址(指针)。这和创建线程是一致的!@
注意一点:在库里维护不一定要在库里开辟空间,都可以进行malloc的!

再来看pthread_join,如何理解?因为在库内部中线程结束时会直接return,并没有进行资源的释放,所以如果不进行join就会产生内存泄漏!join就是通过tid来找到对应位置来释放资源!

一个线程内部就可以有一个数组来维护一个栈结构!线程就独立的拥有自己的栈结构了!栈空间本质是地址空间的一部分区域!主线程使用自己的栈,新线程使用自己开劈的栈!

在用户层面是线程,内核层是轻量级进程,他们是1 :1的。

  1. lwp是用来调度的单位:具有自己的系统调用,pthread库就是对这些系统调用的封装!
  2. 线程概念的表现是在用户层的!
    在这里插入图片描述

Linux的线程 = pthread库中线程的属性集 + LWP

总的来说,pthread_t tid就是线程属性集合的起始虚拟地址 — 在pthread中进行维护。

3 线程局部存储

接下来我们,谈一下线程的局部存储。
我们设置一个全局变量gval,让新线程和主线程都不断打印,但是只有新线程进行++工作:
在这里插入图片描述
可以看到他们的地址是一致的,因为他们共享地址空间,会访问到同一个gval!

今天如果我们不想让他们看到同一个gval呢?让他们各自拥有一份,我们可以使用编译选项__thread(只在linux有效,只能修饰内置类型)

__thread int gval = 100;

在这里插入图片描述
可以看到此时主线程和新线程就是不一样的gval了!地址也不一样,说明新线程和主线程是各自拥有一份gval

这个全局变量,在加上__thread后就会在线程局部存储中各自储存一份,互不干扰!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

相关文章:

  • .NET Core 发展历程和版本迭代
  • 人工蜂鸟算法(Artificial Hummingbird Algorithm,AHA)及其Python和MATLAB实现
  • HTML前端 盒模型及常见的布局 流式布局 弹性布局 网格布局
  • Linux下git入门操作
  • springboot在加了mapper之后报错
  • 第六章:支持向量机
  • 国科大作业考试资料-人工智能原理与算法-2024新编-第十二次作业整理
  • opencv 按键开启连续截图,并加载提示图片
  • 论文写作之latex配置(VSCODE+TEXT LIVE)
  • THS配置keepalive(yjm)
  • JAVA用TreeMap实现JSON按字母升序排序
  • MySQL中的DQL
  • 配置sublime的中的C++编译器(.sublime-build),实现C++20
  • C#初级——结构体
  • Linux中的三类读写函数
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • input的行数自动增减
  • javascript面向对象之创建对象
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Python连接Oracle
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 前端知识点整理(待续)
  • 让你的分享飞起来——极光推出社会化分享组件
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 译有关态射的一切
  • postgresql行列转换函数
  • #if 1...#endif
  • #Ubuntu(修改root信息)
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • #如何使用 Qt 5.6 在 Android 上启用 NFC
  • #微信小程序(布局、渲染层基础知识)
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (30)数组元素和与数字和的绝对差
  • (AngularJS)Angular 控制器之间通信初探
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (Forward) Music Player: From UI Proposal to Code
  • (pycharm)安装python库函数Matplotlib步骤
  • (八)Spring源码解析:Spring MVC
  • (二)c52学习之旅-简单了解单片机
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (六)vue-router+UI组件库
  • (转)mysql使用Navicat 导出和导入数据库
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转)原始图像数据和PDF中的图像数据
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • ./configure、make、make install 命令
  • ./indexer: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object fil
  • .gitattributes 文件
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .net core使用ef 6