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.rhkb.cn/news/446213.html

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

相关文章

如何批量从sql语句中提取表名

简介 使用的卢易表 的提取表名功能&#xff0c;可以从sql语句中批量提取表名。采用纯文本sql语法分析&#xff0c;无需连接数据库&#xff0c;支持从含非sql语句的文件文件中提取&#xff0c;支持各类数据库sql语法。 特点 快&#xff1a;从成百个文件中提取上千个表名只需1…

JAVA开发中SpringMVC框架的使用及常见的404问题原因以及SpringMVC框架基于注解的开发实例

一、JAVA开发中SpringMVC框架的使用及常见的404问题原因 使用SpringMVC建立一个web项目&#xff0c;在IDEA中file->new->project建立一个空项目project。不用选择create from archetype从模板创建。然后在项目的pom.xml中添加公共的依赖包括org.springframework&#xff…

400行程序写一个实时操作系统RTOS(开篇)

笔者之前突发奇想&#xff0c;准备写一个极其微小的实时操作系统内核&#xff0c;在经过数天的努力后&#xff0c;这个RTOS诞生了。令读者比较意外的是&#xff0c;它的程序只有400行左右。但就是这短短的400行&#xff0c;完成了动态内存管理、多线程、优先级、临界区、低功耗…

【原创】Android Studio 中安装大模型辅助编码插件:通义灵码

在 Android Studio 中内置了 Ginimi 预览版&#xff0c;但需要“加速器”才可使用。 在国内有平替的软件同样可以使用&#xff0c;比如 阿里的通义灵码&#xff0c;智谱的CodeGeeX等&#xff0c;从功能和使用上来说都是大同小异。 这里我们以通义灵码为例来讲解其安装和使用 通…

最新Prompt预设词指令教程大全ChatGPT、AI智能体(300+预设词应用)

使用指南 直接复制在AI工具助手中使用&#xff08;提问前&#xff09; 可以前往已经添加好Prompt预设的AI系统测试使用&#xff08;可自定义添加使用&#xff09; SparkAi系统现已支持自定义添加官方GPTs&#xff08;对专业领域更加专业&#xff0c;支持多模态文档&#xff0…

github下载文件的两种方式(非git形式)

1.以下面的图为例 &#xff0c;可以直接点击右上方的绿色Code按键&#xff0c;在弹出的列表中选择Download Zip选项&#xff0c;即可下载。 2.如果下载的是单独的某一个文件&#xff0c;则可以按照下图的格式点击下图所示的那个下载的图标即可。

IP地址如何支持远程办公?

由于当今社会经济的飞速发展&#xff0c;各个方向的业务都不免接触到跨省、跨市以及跨国办公的需要&#xff0c;随之而来的远程操作的不方便&#xff0c;加载缓慢&#xff0c;传输文件时间过长等困难&#xff0c;如何在万里之外实现远程办公呢&#xff1f;我们以以下几点进行阐…

C3D网络介绍及代码撰写详解(总结3)

可以从本人以前的文章中可以看出作者以前从事的是嵌入式控制方面相关的工作&#xff0c;是一个机器视觉小白&#xff0c;之所以开始入门机器视觉的学习主要是一个idea&#xff0c;想把机器视觉与控制相融合未来做一点小东西。废话不多说开始正题。&#xff08;如有侵权立即删稿…

初级前端面试(2)

1.讲一下闭包相关知识&#xff0c;和普通函数有什么区别 闭包是什么&#xff1a;JS中内层函数可以访问外层函数的变量&#xff0c;外层函数无法操作内存函数的变量的特性。我们把这个特性称作闭包。 闭包的好处&#xff1a; 隔离作用域&#xff0c;保护私有变量&#xff1b;…

解决海外社媒风控问题的工具——云手机

随着中国企业逐步进入海外市场&#xff0c;海外社交媒体的风控问题严重影响了企业的推广效果与账号运营。这种背景下&#xff0c;云手机作为一种新型技术解决方案&#xff0c;正日益成为企业应对海外社媒风控的重要工具。 由于海外社媒的严格监控&#xff0c;企业经常面临账号流…

数据库的相关知识

数据库的相关知识 1.数据库能够做什么&#xff1f; 存储大量数据&#xff0c;方便检索和访问保持数据信息的一致、完整共享和安全通过组合分析&#xff0c;产生新的有用信息 2.数据库作用&#xff1f; 存储数据、检索数据、生成新的数据 3.数据库要求&#xff1f; 统一、…

【Windows】【DevOps】Windows Server 2022 安装ansible,基于powershell实现远程自动化运维部署 入门到放弃!

目标服务器安装openssh server参考 【Windows】【DevOps】Windows Server 2022 在线/离线 安装openssh实现ssh远程登陆powershell、scp文件拷贝-CSDN博客 注意&#xff1a;Ansible不支持Windows操作系统部署 根据官方说明&#xff1a; Windows Frequently Asked Questions —…

云计算(第二阶段):mysql后的shell

第一章&#xff1a;变量 前言 什么是shell Shell 是一种提供用户与操作系统内核交互的工具&#xff0c;它接受用户输入的命令&#xff0c;解释后交给操作系统去执行。它不仅可以作为命令解释器&#xff0c;还可以通过脚本完成一系列自动化任务。 shell的特点 跨平台&#xff1a…

人工智能长期记忆的新突破:HippoRAG的创新框架

人工智能咨询培训老师叶梓 转载标明出处 大模型&#xff08;LLMs&#xff09;在预训练后&#xff0c;如何有效地整合大量新经验&#xff0c;同时避免灾难性遗忘&#xff0c;一直是人工智能领域的难题。尽管已有的检索增强生成&#xff08;RAG&#xff09;方法为LLMs提供了长期…

Qt事件——鼠标事件

通过label来显示各种事件 鼠标按下事件 //按下显示坐标 void MyLabel::mousePressEvent(QMouseEvent * ev) {int i ev->x();int j ev->y();//判断按下的鼠标键位if (ev->button() Qt::LeftButton) {qDebug() << "LeftButton";}else if (ev->bu…

SpringCloud-服务治理-Eureka

本篇是从基础方便讲解一些springcloud-服务治理-Eureka中的一些理论性的故事&#xff1b;具体的代码不详细展示&#xff1b;后面的文章会将源码进行整理&#xff0c;并且将源码的github地址上传。 1.什么是服务治理 专治分布式系统 (一)高可用性&#xff1a;服务治理框架保证…

【环境搭建】远程服务器搭建ElasticSearch

参考&#xff1a; 非常详细的阿里云服务器安装ElasticSearch过程..._阿里云服务器使用elasticsearch-CSDN博客 服务器平台&#xff1a;AutoDL 注意&#xff1a; 1、切换为非root用户&#xff0c;su 新用户名&#xff0c;否则ES无法启动 2、安装过程中没有出现设置账号密码…

【微服务】springboot远程docker进行debug调试使用详解

目录 一、前言 二、线上问题常用解决方案 2.1 微服务线上运行中常见的问题 2.2 微服务线上问题解决方案 2.3 远程debug概述 2.3.1 远程debug原理 2.3.2 远程debug优势 三、实验环境准备 3.1 搭建springboot工程 3.1.1 工程结构 3.1.2 引入基础依赖 3.1.3 添加配置文…

Linux之如何找回 root 密码?

1、启动系统&#xff0c;进入开界面&#xff0c;在界面中按“e"进入编辑界面 2、进入编辑界面&#xff0c;使用键盘上的上下键把光标往下移动&#xff0c;找到以”Linux16“开通内容所在的行数&#xff0c;在行的最后面输入&#xff1a;init/bin/sh 3、输入完成后&…