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

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的,一旦设置由零变一, 就是线程分离。 而线程分离呢, 说是分离, 但是其实和原本的进程还是在共享一份资源, 只是这个线程处于分离状态, 线程退出和进程没有关系了!

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

相关文章:

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

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

npm install报错一堆sass gyp ERR!

执行npm install &#xff0c;出现一堆gyp含有sass错误的情况下。 解决办法&#xff1a; 首页可能是node版本问题&#xff0c;太高或者太低&#xff0c;也会导致npm install安装错误&#xff08;不会自动生成node_modules文件&#xff09;&#xff0c;本次试验&#xff0c;刚开…...

微知-BlueField DPU在lspci中显示Flash Recovery是什么意思?

效果&#xff1a; lspci |grep BlueField10:00.0 Memory controller: Mellanox Technologies MT42822 Family [BlueField-2 SoC Flash Recovery] (rev 01)*原因&#xff1a; 表示此时flash是empty空的&#xff0c;或者在flash中的FW是无法工作的。比如烧录错误。 这里指的一提…...

【前端知识点】前端笔记

css 引入css文件的文件路径 <!-- 引入外部 CSS 文件 --> <!-- 当前文件所在文件夹目录 --> <link rel"stylesheet" href"./"> <!-- 当前文件所在父文件夹目录 --> <link rel"stylesheet" href"../">j…...

Sping Cache 使用详解

缓存是提升应用性能的常用手段。它通过将耗时的操作结果存储起来&#xff0c;下次请求可以直接从缓存中获取&#xff0c;从而避免重复计算或查询数据库&#xff0c;显著减少响应时间和服务器负载。Spring 框架提供了强大的缓存抽象 Spring Cache&#xff0c;它简化了缓存的使用…...

动手学深度学习60 机器翻译与数据集

1. 机器翻译与数据集 import os import torch from d2l import torch as d2l#save d2l.DATA_HUB[fra-eng] (d2l.DATA_URL fra-eng.zip,94646ad1522d915e7b0f9296181140edcf86a4f5)#save def read_data_nmt():"""载入“英语&#xff0d;法语”数据集"&qu…...

Python网络爬虫技术

Python网络爬虫技术详解 引言 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;又称网络蜘蛛&#xff08;Web Spider&#xff09;或网络机器人&#xff08;Web Robot&#xff09;&#xff0c;是一种按照一定规则自动抓取互联网信息的程序或脚本。它们通过遍历网页链…...

黑马程序员-redis项目实践笔记1

目录 一、 基于Session实现登录 发送验证码 验证用户输入验证码 校验登录状态 Redis代替Session登录 发送验证码修改 验证用户输入验证码 登录拦截器的优化 二、 商铺查询缓存 缓存更新策略 数据库和缓存不一致解决方案 缓存更新策略的最佳实践方案 实现商铺缓…...

ES-入门聚合查询

url 请求地址 http://192.168.1.108:9200/shopping/_search {"aggs": { //聚合操作"price_group":{ //名称,随意起名"terms":{ //分组"field": "price" //分组字段}}} } 查询出来的结果是 查询结果中价格的平均值 {&q…...

七维大脑: 探索人类认知的未来之路

七维大脑&#xff1a; 探索人类认知的未来之路 随着科技的不断发展&#xff0c;人们对于大脑的认知也在不断扩展。近年来&#xff0c;科学家们提出了一个名为“七维大脑”的概念&#xff0c;试图通过七个维度来理解人类的认知过程。这个概念的提出&#xff0c;让人们开始思考&…...

spring |Spring Security安全框架 —— 认证流程实现

文章目录 开头简介环境搭建入门使用1、认证1、实体类2、Controller层3、Service层3.1、接口3.2、实现类3.3、实现类&#xff1a;UserDetailsServiceImpl 4、Mapper层3、自定义token认证filter 注意事项小结 开头 Spring Security 官方网址&#xff1a;Spring Security官网 开…...

Django+vue自动化测试平台---正式开源!!!

自动化测试&#xff1a;接口、Web UI 与 App 的全面探索 在此郑重声明&#xff1a;本文内容未经本人同意&#xff0c;不得随意转载。若有违者&#xff0c;必将追究其法律责任。同时&#xff0c;禁止对相关源码进行任何形式的售卖行为&#xff0c;本内容仅供学习使用。 Git 地…...

电子电气架构 --- 智能网联汽车未来是什么样子?

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...

docker安装elasticsearch(es)+kibana

目录 docker安装elasticsearch 一.准备工作 1.打开docker目录 2.创建elasticsearch目录 3.打开elasticsearch目录 4.拉取elasticsearch镜像 5.检查镜像 二.挂载目录 1.创建数据挂载目录 2.创建配置挂载目录 3.创建插件挂载目录 4.权限授权 三.编辑配置 1.打开con…...

大厂面试真题-说说redis的雪崩、击穿和穿透

缓存雪崩、击穿、穿透是缓存系统中常见的三种问题&#xff0c;它们都会对系统的性能和稳定性造成严重影响。以下是对这三种问题的详细解释以及相应的解决方案&#xff1a; 一、缓存雪崩 问题解释&#xff1a; 缓存雪崩指的是因为某些原因导致缓存中大量的数据同时失效或过期…...

【Spring】获取Cookie和Session(@CookieValue()和@SessionAttribute())

获取 Cookie 传统获取 Cookie 这是没有 Spring 的时候&#xff0c;用 Servlet 来获取&#xff08;获取所有的 Cookie&#xff09; Spring MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;也是在 Servlet 的基础上实现的 RequestMapping("/getcookie") …...

【C++打怪之路Lv8】-- string类

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;重生之我在学Linux&#xff0c;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持…...

【JS】node.js压缩文件的方式

在 Node.js 中&#xff0c;有多种方法可以压缩文件。以下是几种常见的压缩方式及其对应的代码示例&#xff1a; 使用 archiver 压缩成 ZIP 文件使用 zlib 压缩成 GZIP 文件使用 tar 压缩成 TAR 文件 1. 使用 archiver 压缩成 ZIP 文件 archiver 是一个功能强大的库&#xff…...

2024免费mac苹果电脑清理垃圾软件CleanMyMac X4.15.8

对于苹果电脑用户来说&#xff0c;设备上积累的垃圾文件可能会导致存储空间变得紧张&#xff0c;影响电脑的性能和使用体验。尤其是那些经常下载和安装新应用、编辑视频或处理大量照片的用户&#xff0c;更容易感受到存储空间的压力。面对这种情况&#xff0c;寻找一种有效的苹…...

MPA-SVM多变量回归预测|海洋捕食者优化算法-支持向量机|Matalb

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译&am…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...