linux线程 | 线程的控制(二)

        前言: 本节内容是线程的控制部分的第二个小节。 主要是列出我们的线程控制部分的几个细节性问题以及我们的线程分离。这些都是需要大量的代码去进行实验的。所以, 准备好接受新知识的友友们请耐心观看。 现在开始我们的学习吧。

        ps:本节内容适合了解线程的基本控制(创建, 等待, 终止)的友友们进行观看哦。 

目录

线程的栈

准备文件

makefile

核心代码

创建test_i栈区变量

利用全局变量拿到别的执行流数据  

局部性存储

线程分离

主线程分离

自己分离自己 


        首先我们的系统之中,有下面四种情况。 

        左上角是只有一个线程一个进程的情况, 右上角是一个进程多个线程的情况。 左下角是多个进程里面有一个线程的情况。 右下角是多个进程里面有多个进程的情况。

        那么, 其实我们的linux当中, 其实是分为用户级线程和内核LWP。 这两个加起来, 才是我们的linux下真正的线程。 其中, 我们的linux其实是属于用户级线程。 里面的用户级线程与内核LWP的比率为 1 : 1

线程的栈

        现在我们谈一谈这个栈, 这个栈并不是简简单单的用来入栈出栈, 定义变量。 实际上, 我们的每一条执行流的本质就是一条调用链, 从main函数开始从上往下执行, 我们会依次执行各种函数, 当我们进行调用函数时, 本质上就是在栈当中先为该函数形成一个独立的栈帧结构。 所以这个栈其实就是被整体使用的, 依次把一个一个地调用链所对应的栈帧结构宏观上在栈上依次开辟。 然后我们每一次定义变量, 都是在栈帧结构里面去定义的, 这个栈结构, 本质是为了支持我们在应用层来完成我们的整个的调用链所对应的临时空间的开辟和释放。 所以, 这些线程为了能够拥有独立的调用链, 就必须拥有属于自己的调用栈!

        现在我们利用代码来测试一下:

准备文件

        准备好两个文件

makefile

        再将makefile准备出来

mythread.exe:mythread.cppg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -rf mythread.exe

核心代码

        这串代码分为几个板块: 定义线程的信息的结构体、线程信息的初始化、将整形转化为字符串类型、线程的执行代码、主函数

#include<iostream>
using namespace std;
#include<pthread.h>
#include<vector>
#include<unistd.h>#define NUM 5  //创建多个执行流, NUM为执行流个数using namespace std;//线程的数据信息。 
struct threadData
{string threadname;
};//将整形以十六进制转化为字符串类型
string toHex(pthread_t tid)
{char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", tid);return buffer;
}//线程信息的初始化
void InitthreadData(threadData* td, int number)
{td->threadname = "thread-" + to_string(number);
}//新线程的执行代码
void* threadRuntine(void* args)
{threadData* td = static_cast<threadData*>(args);int i = 0;while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self()) << ", name: " << td->threadname << endl;i++;sleep(2);}delete td;return nullptr;
}int main()
{   vector<threadData*> tids;//我们创建多个执行流, 为了能够验证每个线程都有一个独立的栈结构for (int i = 0; i < NUM; i++){//每一个线程都要有一个线程的信息, 并且这个线程的信息我们在堆区开辟, 那么所有的线程其实都能够看到这个线程的信息, 因为堆区是共享的。threadData* td = new threadData();pthread_t tid;InitthreadData(td, i); //初始化线程的信息。pthread_create(&tid, nullptr, threadRuntine, td);tids.push_back(tid);sleep(2);}for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

然后我们就能看到这种情况。

创建test_i栈区变量

        在线程的执行代码块里面添加一个test_i变量, 然后打印这个变量。 

//新线程的执行代码
void* threadRuntine(void* args)
{threadData* td = static_cast<threadData*>(args);int test_i = 0;int i = 0;while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self()) << ", name: " << td->threadname << ", test_i: " << test_i << ", &test_i: " << &test_i << endl;i++;test_i++;sleep(2);}delete td;return nullptr;
}

        下面就是运行结果, 从图中我们可以看到, 每一个执行流都有自己的独有的一份test_i, 并且他们的值都是从零开始, 一直加到4。而且, 每个变量的地址都不一样, 所以每个线程都会有自己独立的栈结构。当我们的线程执行到threadRuntine, 就会在自己的栈结构里面开辟自己的栈帧, 然后创建test_i也是在自己刚刚创建的栈帧中创建。 

利用全局变量拿到别的执行流数据  

        创建一个全局变量p

        然后在线程执行的代码里面, 写上要拿哪一个线程的什么数据:

        为了确认真正的拿到了这个数据, 在程序的最后打印这个数据:

下面是运行结果:

        由上面的结果我们其实就能够知道:在线程中根本没有秘密, 只不过要求线程有独立的栈, 但是这个独立的栈本质上还是在地址空间的共享区中。 所以, 我们每个线程叫做都有一个独立的栈结构, 而不是一个私有的栈结构。 就是因为这个栈结构能够被别人访问到, 而私有的意思是别人看不到。 ——所以, 线程与线程之间没有秘密。 线程的栈上的数据,也是可以被其他线程看到并访问的。 

局部性存储

        我们之前说过, 全局变量是可以被所有线程看到并访问的。但是如果线程想要一个私有的全局变量呢? 那么我们就需要在全局变量前面加一个__thread。 下面用代码来进行验证:

        我们的核心代码还是上面写的代码。

        并且为了方便观察, 将创建线程每隔1000微秒(使用usleep函数)创建一个线程。 然后每隔2秒打印一次数据:

#include<iostream>
using namespace std;
#include<pthread.h>
#include<vector>
#include<unistd.h>#define NUM 5  //创建多个执行流, NUM为执行流个数using namespace std;int* p = nullptr;
__thread int g_val = 0;//线程的数据信息。 
struct threadData
{string threadname;
};string toHex(pthread_t tid)
{char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", tid);return buffer;
}void InitthreadData(threadData* td, int number)
{td->threadname = "thread-" + to_string(number);
}//新线程的执行代码
void* threadRuntine(void* args)
{threadData* td = static_cast<threadData*>(args);int i = 0;while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self()) << ", name: " << td->threadname<< ", g_val: " << g_val << ", &g_val: " << &g_val << endl;i++;g_val++;sleep(2);}delete td;return nullptr;
}int main()
{   vector<pthread_t> tids;//我们创建多个执行流, 为了能够验证每个线程都有一个独立的栈结构for (int i = 0; i < NUM; i++){threadData* td = new threadData();pthread_t tid;InitthreadData(td, i);pthread_create(&tid, nullptr, threadRuntine, td);tids.push_back(tid);usleep(1000);}//for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

        下面是运行结果, 运行结果中g_val都是从0开始, 然后各自加各自的,互不影响。 而且每个g_val的地址也不相同。这里的这个__thread, 叫做编译选项。每一个线程都访问同一个全局变量, 但是在访问的时候, 每一个全局变量对于每一个线程来说, 都是各自私有一份的。 这种技术叫做线程的局部性存储!

       另外, 我们需要知道的一点就是__thread只能修饰内置类型, 不能修饰自定义类型。 

       那么, 这个局部性存储有什么作用呢? 就比如我们的线程要进行多次函数调用并且函数都要用到它,而且又不想和别的线程共享这份资源的时候, 我们就可以使用线程的局部性存储。

        

线程分离

        在我们的默认情况下, 新创建的线程是joinable的, 线程退出后, 需要对其进行pthread_join操作, 否则无法释放资源造成内存泄露。 但是我们可以告诉操作系统, 当进程退出的时候, 不需要主线程等待, 而是自动释放资源, 这个操作就是线程分离。 

        接口如下:

        参数就是线程的tid。 返回值和之前一样,就是成功零被返回, 失败返回错误码。

主线程分离

        然后我们测试一下线程分离, 代码只改变main函数里面的就可以。 主要就是在进行线程等待之前先将线程分离。 然后等待的时候就会等待错误, 返回错误码。同时我们也可以打印一下错误码观察错误信息。


int main()
{   vector<pthread_t> tids;//我们创建多个执行流, 为了能够验证每个线程都有一个独立的栈结构for (int i = 0; i < NUM; i++){threadData* td = new threadData();pthread_t tid;InitthreadData(td, i);pthread_create(&tid, nullptr, threadRuntine, td);tids.push_back(tid);usleep(1000);}//for (auto e : tids){pthread_detach(e);}for (int i = 0; i < tids.size(); i++){int n = pthread_join(tids[i], nullptr);cout << "n = " << n << ", who: " << toHex(tids[i]) << ", " << strerror(n) << endl;}return 0;
}

        运行结果如下, 可以发现运行结果如同我们的猜测, 都是返回错误码。 然后我们可以打印一下

自己分离自己 

        上面的情况是在主线程分离新线程。 我们也可以在新线程里面自己分离自己。 

//新线程的执行代码
void* threadRuntine(void* args)
{pthread_detach(pthread_self());//threadData* td = static_cast<threadData*>(args);number = pthread_self();int i = 0;while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(number) << ", name: " << td->threadname<< ", g_val: " << g_val << ", &g_val: " << &g_val << endl;i++;g_val++;sleep(2);}delete td;return nullptr;
}

        然后我们的结果其实和上面的是一样的:

        其实线程的分离, 线程是否分离其实是一种属性状态。 一开始默认线程是不分离的,是joinable的。本质上就是线程库里面的线程数据结构里有一个是否可分离的标记位, 开始默认是joinable的,一旦设置由零变一, 就是线程分离。 而线程分离呢, 说是分离, 但是其实和原本的进程还是在共享一份资源, 只是这个线程处于分离状态, 线程退出和进程没有关系了!

  ——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!  

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

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

相关文章

云轴科技ZStack入选信通院《高质量数字化转型产品及服务全景图》AI大模型图谱

近日&#xff0c;由中国互联网协会中小企业发展工作委员会主办的“2024大模型数字生态发展大会暨铸基计划年中会议”在北京成功召开。会上发布了中国信通院在大模型数字化等领域的多项工作成果&#xff0c;其中重点发布了《高质量数字化转型产品及服务全景图&#xff08;2024上…

前端开发笔记--html 黑马程序员1

文章目录 前端开发工具--VsCode前端开发基础语法VsCode优秀插件Chinese --中文插件Auto Rename Tag --自动重命名插件open in browserOpen in Default BrowserOpen in Other Browser Live Server -- 实时预览 前端开发工具–VsCode 轻量级与快速启动 快速加载&#xff1a;VSCo…

怎么ping网络ip地址通不通

怎么Ping网络IP地址通不通&#xff1f;要检查网络中的IP地址是否连通&#xff0c;可以使用‌Ping命令。Ping命令通过发送ICMP&#xff08;Internet Control Message Protocol&#xff0c;因特网控制消息协议&#xff09;Echo请求报文并等待回应&#xff0c;来判断目标主机是否可…

微知-Mellanox网卡如何导出firmware中的config文件以及文件中有些什么?(ini配置文件,8个区)

背景 Mellanox网卡早期版本以及Engineer simple的DPU支持导出配置文件&#xff0c;该配置文件就是用来告诉firmware的行为。但不是mlxconfig真正设置的文件(mlxconfig -d xxx -e -q应该就是把这个文件读取出来&#xff0c;并且有3个文件&#xff0c;包括默认的&#xff0c;当前…

WebGl学习使用attribute变量绘制一个水平移动的点

在WebGL编程中&#xff0c;attribute变量是一种特殊类型的变量&#xff0c;用于从客户端传递数据到顶点着色器。这些数据通常包括顶点的位置、颜色、纹理坐标等&#xff0c;它们是与每个顶点直接相关的信息。attribute变量在顶点着色器中声明&#xff0c;并且对于每个顶点来说都…

【linux】信号(下)

8. 阻塞信号 (一)信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)进程可以选择阻塞 (Block )某个信号被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作(即被阻塞的信…

如何在阿里云一键部署FlowiseAI

什么是FlowiseAI FlowiseAI 是一个开源的低代码开发工具&#xff0c;专为开发者构建定制的语言学习模型&#xff08;LLM&#xff09;应用而设计。 通过其拖放式界面&#xff0c;用户可以轻松创建和管理AI驱动的交互式应用&#xff0c;如聊天机器人和数据分析工具。 它基于Lang…

c++速成之从string类中获取那些知识

温馨提示&#xff1a;本篇文章依旧是c速成系列的文章&#xff0c;因为从这里开始&#xff0c;内容就已经开始复杂&#xff0c;但博主还是以是什么&#xff0c;怎么用的原则继续给大家讲解知识点&#xff0c;希望大家能够耐心看完&#xff0c;并给博主留个三连&#xff0c;博主先…

Redis-缓存过期淘汰策略

缓存淘汰策略 生产上redis内存设置为多少 设置为最大内存的 3/4 redis 会占用物理机多少内存 默认大小是 0&#xff0c;64 位系统下表示不限制内存大小&#xff0c;32位系统表示 3G 如何设置修改redis内存大小 config get maxmemory 查看修改方式 配置文件 单位是字节 2.…

深入Postman- 自动化篇

前言 在前两篇博文《Postman使用 - 基础篇》《玩转Postman:进阶篇》中,我们介绍了 Postman 作为一款专业接口测试工具在接口测试中的主要用法以及它强大的变量、脚本功能,给测试工作人员完成接口的手工测试带来了极大的便利。其实在自动化测试上,Postman 也能进行良好的支…

校车购票微信小程序的设计与实现(lw+演示+源码+运行)

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

软考《信息系统运行管理员》- 4.1信息系统软件运维概述

4.1信息系统软件运维概述 文章目录 4.1信息系统软件运维概述信息系统软件运维的概念信息系统软件的可维护性及维护类型对软件可维护性的度量可以从以下几个方面进行&#xff1a;软件维护分类&#xff1a; 信息系统软件运维的体系1.**需求驱动**2.**运维流程**3.**运维过程**4.*…

斯坦福 CS229 I 机器学习 I 构建大型语言模型 (LLMs)

1. Pretraining -> GPT3 1.1. Task & loss 1.1.1. 训练 LLMs 时的关键点 对于 LLMs 的训练来说&#xff0c;Architecture&#xff08;架构&#xff09;、Training algorithm/loss&#xff08;训练算法/损失函数&#xff09;、Data&#xff08;数据&#xff09;、Evalu…

两种方式创建Vue项目

文章目录 引言利用Vue命令创建Vue项目准备工作安装Vue CLI创建Vue项目方法一&#xff1a;使用vue init命令方法二&#xff1a;使用vue create命令启动Vue项目 利用Vite工具创建Vue项目概述利用Vite创建项目启动项目 结语 引言 大家好&#xff0c;今天我将向大家展示如何使用不…

【layui】多文件上传组件实现

插件预览效果&#xff1a; 需要引入layui的脚本文件layui.js和样式文件layui.css html代码&#xff1a; <div class"layui-input-block"><div class"layui-upload-list"><table class"layui-table"><colgroup><col…

vue3中如何更改当前类的文件名称

首先&#xff0c;使用script指定文件名称 <template><div class"person"><h2>姓名&#xff1a;{{ name }}</h2><h2>年龄&#xff1a;{{ age }}</h2><button click"showTel">查看联系方式</button><bu…

VS2017 编译 SQLite3 动态库

首先官方下载源码: Tags sqlite/sqlite (github.com) 1.安装 VS2017 community edition 2.打开VS2017命令行工具 3.安装TCL 开发库,推荐 TCL 9.0 先下载源码: Tcl/Tk 9.0 使用vs2017编译tcl&

【STM32-HAL库】实现微秒、毫秒、纳秒延时。(STM32F4系列)(附带工程下载链接)

使用了本代码后不能使用HAL库自带的HAL_Delay函数 使用了本代码后不能使用HAL库自带的HAL_Delay函数 使用了本代码后不能使用HAL库自带的HAL_Delay函数 一、新建工程 可以参考我的新建工程系列教程 stm32-HAL库cubeMX新建工程教程&#xff08;以F103C8T6为例&#xff09;ht…

基于IDEA+SpringBoot+Vue+Uniapp的投票评选小程序系统的详细设计和实现

2. 详细视频演示 文章底部名片&#xff0c;联系我获取更详细的演示视频 3. 论文参考 4. 项目运行截图 代码运行效果图 代码运行效果图 代码运行效果图 代码运行效果图 代码运行效果图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框…

Flink 批作业如何在 Master 节点出错重启后恢复执行进度?

摘要&#xff1a;本文撰写自阿里云研发工程师李俊睿&#xff08;昕程&#xff09;&#xff0c;主要介绍 Flink 1.20 版本中引入了批作业在 JM failover 后的进度恢复功能。主要分为以下四个内容&#xff1a; 背景解决思路使用效果如何启用 一、背景 在 Flink 1.20 版本之前&am…