【线程】线程的控制

本文重点:理解线程控制的接口

前言

内核中是没有很明确线程的概念的,只有轻量级进程的概念,不会提供直接给我们线程的系统调用,而会给我们提供轻量级进程的系统调用。我们用户是需要线程的接口的,在应用层,人们封装了轻量级进程的系统调用,为用户直接提供线程的接口,这个封装的就是线程库pthread库,这个是第三方库,几乎所有的Linux系统都会自带这个库,当我们进行编译链接时,要指定这个库

线程的创建

函数pthread_create

参数:

thread:输出型参数,返回线程ID
attr:设置线程的属性,attr为nullptr表示使用默认属性
start_routine:是个函数指针,线程启动后要执行的函数
arg:传给线程启动函数的参数,线程被创建成功,新线程回调线程函数的时候需要参数,这个参数是给线程函数传递的

返回值:成功返回0,失败返回错误码

传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他线程函数会这样做)。而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小

线程的函数的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>using namespace std;void* threadroutine(void * arg)
{const char* name=(const char*)arg;while(true){cout<<name<<",pid: "<<getpid()<<endl;sleep(2);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadroutine,(void*)"new thread");while(true){cout<<"main thread,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

发现它们的pid是一样的

ps  -aL   查看系统轻量级进程 

LWP(light weight process):线程ID,这个线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

我们发现一个PID和LWP是一样的,那这个就是主线程,其他就是创建出来的线程 

线程组内的第一个线程,在用户态被称为主线程(main thread),内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程

至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样

强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系

线程的终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 线程可以调用pthread_ exit终止自己。
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

注意:以前的进程终止函数exit是用来终止进程的

函数pthread_exit

参数和线程函数的返回值类型是一样的都是void*,这就和return终止线程差不多了

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

函数pthread_cancel

取消执行中的线程

线程的等待

在进程里有个进程等待,父进程等待子进程,是为了防止僵尸进程,回收子进程,防止内存泄漏

为什么需要线程等待?已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。那么线程中也要有线程等待,如果没有,那么也会有类似于僵尸的状态

函数pthread_join

函数的作用:等待指定线程,可以获取这个线程函数的返回值void*(终止状态)

参数:
thread:线程ID
value_ptr:它指向一个指针,就是指向线程创建时,调用的线程函数的返回值void*
返回值:成功返回0;失败返回错误码

调用该函数的线程将阻塞等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED(一个宏常数,就是-1)。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。 

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>using namespace std;void* threadroutine(void * arg)
{const char* name=(const char*)arg;int cnt=5;while(true){cout<<name<<",pid: "<<getpid()<<endl;sleep(1);cnt--;if(cnt==0) break;}return (void*)1;//我的当前平台,地址是64位,8字节,而int是4字节,不进行强转会导致混乱
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadroutine,(void*)"new thread");void * retval;pthread_join(tid,&retval);// main thread等待的时候,默认是阻塞等待的!cout << "main thread quit ..., ret: " << (long long int)retval << endl;return 0;
}

为什么我们在这里join的时候不考虑异常呢??因为做不到,线程异常了整个进程都会挂掉。线程的健壮性低

线程的分离

上面的线程等待,只能阻塞等待,那如果不想阻塞怎么办,并且不关心线程的返回值,那就让线程分离,线程分离之后,主线程就不管了,主线程就干自己的事了,自己的资源自己释放回收了,虽然线程分离了,但是还是属于这个进程的

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离

joinable和分离是冲突的,一个线程不能既是joinable又是分离的,也就是说,线程分离了,主线程就不用等待回收线程了

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
void* PthreadRoutine(void* args)
{int i=0;pthread_detach(pthread_self());while(i<3){cout<<"child thread ,pid: "<<getpid()<<endl;i++;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,PthreadRoutine,nullptr);sleep(1);//确保分离成功int n=pthread_join(tid,nullptr);printf("n = %d, who = 0x%x, why: %s\n", n, tid, strerror(n));return 0;}

线程ID及进程地址空间布局

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID

函数pthread_self

函数clone 

这个函数clone是系统调用,用来创建轻量级进程的,前言说的pthread库的底层就是用这个函数封装的

使用线程库,是要加载到内存的,它是动态库,所以会在地址空间的共享区

线程的概念是库给我们维护的,线程库注定要维护多个线程,线程库要管理这些线程,先描述再组织,每个线程都会有一个库级别的结构体tcb,这个结构体的起始地址就是线程的id 

除了主线程外,所有的其他线程的独立栈,都在共享区,具体来讲,是在pthread库中,tid指向用户的tcb。全局变量和堆区都是线程共享的,栈不是,每个线程都有自己的独立栈

目前,我们的原生线程,pthread库,原生线程库,C++11 语言本身也已经支持多线程了 vs 原生线程库。其实C++的多线程底层封装的就是Linux系统下的原生线程库,为什么C++移值性高,就是因为C++的代码在不同平台上都可以跑,在Linux下跑多线程底层就是Linux的原生线程库,在Windows跑,底层就是Windows的原生线程库,C++语音在底层设计时,不同的系统有不同的设计,但是在上层看来都一样

#include <iostream>
#include <thread>using namespace std;void threadrun()
{while(true){cout << "I am a new thead for C++" << endl;sleep(1);}
}int main()
{thread t1(threadrun);t1.join();return 0;
}

多线程的创建

1.创建多线程

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<endl;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

上面代码在主线程开辟的堆空间传递给了子线程,子线程还可以利用,说明堆空间是被所有线程共享的

2.验证全局变量共享

在上面代码上加个全局变量g_val,在子线程打印他的地址

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4
int g_val=0;//定义一个全局class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",g_val: "<<g_val<<",&g_val: "<<&g_val<<endl;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

发现这个全局变量的地址都是一样的,说明全局变量被所有线程共享

3.线程的局部存储

如果线程不想共享全局变量,想要自己私有的全局变量,那么在全局变量加__thread修饰(两个下划线),这个是个编译选项,只能用来定义内置类型,不能修饰自定义类型,那么这个全局变量就不在全局区了,就在线程的栈结构的局部存储中,这个是线程级别的全局变量,也就是在某个线程里面时全局的

 

 地址不一样,并且这个地址挺大的,说明在中间的堆栈之间

4.验证线程具有独立栈结构

在每个线程里面加个变量test_i,打印他的地址

#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;int test_i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",test_i: "<<test_i<<",&test_i: "<<&test_i<<endl;;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

 

发现test_i的地址都不一样,说明它们有独立的栈结构,并不是私有的栈结构,主线程想访问也可以访问的,线程中没有秘密,如果要访问还是可以访问的

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

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

相关文章

【机器学习】12-决策树1——概念、特征选择

机器学习10-决策树1 学习样本的特征&#xff0c;将样本划分到不同的类别&#xff08;分类问题&#xff09;或预测连续的数值&#xff08;回归问题&#xff09;。 选择特征&#xff0c;划分数据集&#xff0c;划分完成形成模型&#xff08;树结构&#xff09;&#xff0c;一个…

仿真软件PROTEUS DESIGN SUITE遇到的一些问题

仿真软件PROTEUS DESIGN SUITE遇到的一些问题 软件网上有很多下载地址自己找哈! 首先如果遇到仿真 没有库 ,需要在网上下载库文件替换到DATA目录下 如果不是默认安装到C盘需要手动修改这些地址,不然会报错!! 当遇到点击仿真出现报错 : 检查这个设置地址是否正确: 随便在库文…

物理学基础精解【7】

文章目录 平面方程直角坐标及基本运算线段的定比分点一、定义二、坐标公式三、特殊情况四、应用举例五、推导过程&#xff08;简要&#xff09;两直线的交点和两曲线的交点两直线的交点两曲线的交点例题&#xff1a;求两直线的交点例题&#xff1a;求两曲线的交点 参考文献 平面…

IPsec-VPN中文解释

一 IPsec-VPN 实操 (点到点) 网络括谱图 IPSec-VPN 配置思路 1 配置IP地址 FWA:IP地址的配置 [FW1000-A]interface GigabitEthernet 1/0/0 [FW1000-A-GigabitEthernet1/0/0]ip address 10.1.1.1 24 //配置IP地址 [FW1000-A]interface GigabitEthernet 1/0/2 [FW10…

Windows安全日志分析(事件ID详解)

目录 如何查看Windows安全日志 常见事件ID列表 事件ID 1116 - 防病毒软件检测到恶意软件 事件ID 4624 - 账户登录成功 事件ID 4625 - 账户登录失败 事件ID 4672 - 为新登录分配特殊权限 事件ID 4688 - 新进程创建 事件ID 4689 - 进程终止 事件ID 4720 - 用户账户创建 …

力扣206.反转链表

力扣《反转链表》系列文章目录 刷题次序&#xff0c;由易到难&#xff0c;一次刷通&#xff01;&#xff01;&#xff01; 题目题解206. 反转链表反转链表的全部 题解192. 反转链表 II反转链表的指定段 题解224. 两两交换链表中的节点两个一组反转链表 题解325. K 个一组翻转…

【Go】Go语言切片(Slice)深度剖析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Geo.__init__() got an unexpected keyword argument ‘title_color‘

把pyecharts从0.5版升级以后&#xff0c;报错如下&#xff1a; lmportError: cannot import name Geo from pyecharts‘ 参考这个&#xff1a;python画图时&#xff0c;from pyecharts import Geo时出错_cannot import name geo from pyecharts-CSDN博客 改成&#xff1a; fr…

yolov5/8/9/10模型在VOC数据集上的应用【代码+数据集+python环境+GUI系统】

yolov5/8/9/10模型在VOC数据集上的应用【代码数据集python环境GUI系统】 1.背景意义 VOC数据集被广泛应用于计算机视觉领域的研究和实验中&#xff0c;特别是目标检测和图像识别任务。许多知名的目标检测算法都使用VOC数据集进行训练和测试。VOC挑战赛&#xff08;VOC Challeng…

Chainlit集成LlamaIndex实现知识库高级检索(自动合并检索)

检索原理 自动合并检索 自动合并检索原理&#xff0c;和我的上一篇文章的检索方案&#xff1a; 将文本分割成512大小&#xff08;一般对应段落大小&#xff09;和128&#xff08;一般对句子大小不是严格的句子长度&#xff09;大小两种分别存储到索引库&#xff0c;再用llama_…

NoSql数据库Redis知识点

数据库的分类 关系型数据库 &#xff0c;是建立在关系模型基础上的数据库&#xff0c;其借助于集合代数等数学概念和方法来处理数据库 中的数据主流的 MySQL 、 Oracle 、 MS SQL Server 和 DB2 都属于这类传统数据库。 NoSQL 数据库 &#xff0c;全称为 Not Only SQL &a…

[uni-app]小兔鲜-01项目起步

项目介绍 效果演示 技术架构 创建项目 HBuilderX创建 下载HBuilderX编辑器 HBuilderX/创建项目: 选择模板/选择Vue版本/创建 安装插件: 工具/插件安装/uni-app(Vue3)编译器 vue代码不能直接运行在小程序环境, 编译插件帮助我们进行代码转换 绑定微信开发者工具: 指定微信开…

2024年最新前端工程师 TypeScript 基础知识点详细教程(更新中)

1. TypeScript 概述 TypeScript 是由微软开发的、基于 JavaScript 的一种强类型编程语言。它是在 JavaScript 的基础上添加了静态类型检查、面向对象编程等功能的超集&#xff0c;最终会被编译为纯 JavaScript 代码。由于其扩展了 JavaScript 的功能&#xff0c;TypeScript 特…

解锁亚马逊测评自养号防关联新技术

解锁亚马逊测评自养号防关联的新技术主要包括以下几个方面&#xff0c;这些技术旨在提高测评过程的安全性&#xff0c;降低账号被关联的风险&#xff1a; 1. 独立纯净IP技术 独立纯净IP&#xff1a;采用独立、纯净且未受污染的国外IP地址&#xff0c;确保这些IP未被标记或列入…

240922-MacOS终端访问硬盘

A. 最终效果 B. 操作步骤 在macOS中&#xff0c;可以通过命令行使用Terminal访问硬盘的不同位置。你可以按照以下步骤操作&#xff1a; 打开终端&#xff08;Terminal&#xff09;&#xff1a; 在应用程序中打开终端&#xff0c;或者使用 Spotlight 搜索“Terminal”来启动。 …

初学者的鸿蒙多线程并发之 TaskPool 踩坑之旅

1. 背景 目标群体&#xff1a;鸿蒙初学者 版本&#xff1a;HarmonyOS 3.1/4.0 背景&#xff1a;鸿蒙 App 的全局路由管理功能&#xff0c;需要在 App 启动时初始化对 raw 下的相关配置文件进行读取、解析并缓存。App 启动时涉及到了大量模块的初始化&#xff0c;好多模块都涉…

巨潮股票爬虫逆向

目标网站 aHR0cDovL3dlYmFwaS5jbmluZm8uY29tLmNuLyMvSVBPTGlzdD9tYXJrZXQ9c3o 一、抓包分析 请求头参数加密 二、逆向分析 下xhr断点 参数生成位置 发现是AES加密&#xff0c;不过是混淆的&#xff0c;但并不影响咱们扣代码 文章仅提供技术交流学习&#xff0c;不可对目标服…

Vue3+Element Plus:使用el-dialog,对话框可拖动,且对话框弹出时仍然能够在背景页(对话框外部的页面部分)上进行滚动以及输入框输入信息

【需求】 使用Element Plus中的el-dialog默认是模态的&#xff08;即它会阻止用户与对话框外部的元素进行交互&#xff09;&#xff0c;对话框弹出时仍然能够在背景页&#xff08;对话框外部的页面部分&#xff09;上进行滚动以及输入框输入信息&#xff0c;且对话框可拖动 【…

react hooks--React.memo

基本语法 React.memo 高阶组件的使用场景说明&#xff1a; React 组件更新机制&#xff1a;只要父组件状态更新&#xff0c;子组件就会无条件的一起更新。 子组件 props 变化时更新过程&#xff1a;组件代码执行 -> JSX Diff&#xff08;配合虚拟 DOM&#xff09;-> 渲…

STM32精确控制步进电机

目的&#xff1a;学习使用STM32电机驱动器步进电机&#xff0c;进行电机运动精确控制。 测试环境&#xff1a; MCU主控芯片STM32F103RCT6 &#xff1b;A4988步进电机驱动器模块&#xff1b;微型2相4线步进电机10mm丝杆滑台&#xff0c;金属丝杆安装有滑块。 10mm二相四线微型…