当前位置: 首页 > 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…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

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

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

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...