【Linux】22. 线程控制

Linux线程控制

POSIX线程库

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

线程创建

pthread_create函数

功能:创建一个新的线程
原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

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

代码练习

#include <iostream>
#include <vector>
#include <unistd.h>using namespace std;// 这里的类当做结构体使用(方便理解)
class ThreadData
{
public:// 编号int number;// tidpthread_t tid;// 将数据刷新到buffer中char namebuffer[64];
};// 函数结构必须和pthread_create提供的接口相同
void *start_routine(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);int cnt = 10;while (cnt){cout << "cnt: " << cnt << " &cnt: " << &cnt << endl;cnt--;// sleep(10);}return nullptr;
}int main()
{// 1. 想要创建一批线程// 放在vector容器中vector<ThreadData *> threads;
#define NUM 10for (int i = 0; i < NUM; i++){// new出ThreadData对象ThreadData *td = new ThreadData();td->number = i + 1;// 打印到namebuffer中snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);// 创建线程// argv参数先不学习 设置为nullptr 创建start_routine回调函数 也就是线程执行任务pthread_create(&td->tid, nullptr, start_routine, td);threads.push_back(td);sleep(10);}for (auto &iter : threads){cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;sleep(1);}return 0;
}

在这里插入图片描述
在这里插入图片描述
一个线程如果出现了异常,会影响其他线程吗?会的(健壮性或者鲁棒性较差)对于信号而言,信号是整体发给进程的!

exit(0); 能不能用来终止线程,不能,因为exit是终止进程的!,任何一个执行流调用exit都会让整个进程退出

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数

功能:线程终止
原型

void pthread_exit(void *value_ptr);

参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

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

pthread_cancel函数

功能:取消一个执行中的线程
原型

int pthread_cancel(pthread_t thread);

参数
thread:线程ID
返回值:成功返回0;失败返回错误码

在这里插入图片描述

线程等待

为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
创建新的线程不会复用刚才退出线程的地址空间。

pthread_join函数

功能:等待线程结束
原型

int pthread_join(pthread_t thread, void **value_ptr);

参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
    在这里插入图片描述
    在这里插入图片描述

线程退出(等待+终止)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>using namespace std;// 这里的类当做结构体使用(方便理解)
class ThreadData
{
public:// 编号int number;// tidpthread_t tid;// 将数据刷新到buffer中char namebuffer[64];
};// 将返回信息定义成对象
class ThreadReturn
{
public:int exit_code;int exit_result;
};// 函数结构必须和pthread_create提供的接口相同
void *start_routine(void *args)
{sleep(3);ThreadData *td = static_cast<ThreadData *>(args);int cnt = 10;while (cnt){cout << "cnt: " << cnt << " &cnt: " << &cnt << endl;cnt--;sleep(1);}// 线程如何终止// delete td;// 线程函数结束时,return的时候线程就算终止了// return nullptr;// 传编号过去// return (void *)6; // 这里会出现waring -- 将整数强转成指针类型 指针类型在64位机器上是8字节的// return (void*)td->number;// pthread_exit((void*)106); --既然假的地址,整数都能被外部拿到,那么如何返回的是,堆空间的地址呢?对象的地址呢?// 定义成对象ThreadReturn *tr = new ThreadReturn();// 自定义tr->exit_code = 1;tr->exit_result = 106;// 不能定义成ThreadReturn tr; --这样就是在栈上开辟空间了return (void*)tr;
}int main()
{// 1. 想要创建一批线程// 放在vector容器中vector<ThreadData *> threads;
#define NUM 10for (int i = 0; i < NUM; i++){// new出ThreadData对象ThreadData *td = new ThreadData();td->number = i + 1;// 打印到namebuffer中snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);// 创建线程// argv参数先不学习 设置为nullptr 创建start_routine回调函数 也就是线程执行任务pthread_create(&td->tid, nullptr, start_routine, td);threads.push_back(td);// sleep(10);}for (auto &iter : threads){cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;// sleep(1);}// 线程也可以被取消!调用pthread_cancel方法// 但是线程要被取消的前提是该线程已经跑起来了!// sleep(5);// 取消一半的线程// for (int i = 0; i < threads.size() / 2; i++)// {//     // int pthread_cancel(pthread_t thread);//     // 线程如果是被取消的 其退出码是-1//     pthread_cancel(threads[i]->tid);//     cout << "pthread_cancel : " << threads[i]->namebuffer << " success" << endl;// }// 线程也是需要等待的,如果不进行等待就会造成类似僵尸进程的问题 -- 内存泄漏// 线程等待的作用:// 1. 获取新线程的退出信息 -- 也可以不关心// 2. 回收新线程的PCB等内核资源,防止内存泄漏 -- 暂时无法查看for (auto &iter : threads){void *ret = nullptr;// pthread_join函数默认调用成功!在线程中不考虑异常问题(异常是进程考虑的)int n = pthread_join(iter->tid, (void **)&ret);// int n = pthread_join(iter->tid, &ret);// 若是等待不成功那就直接报错assert(n == 0);cout << " join : " << iter->namebuffer << " success ,exit_code: " << ((ThreadReturn*)ret)->exit_code << " exit_result: " << ((ThreadReturn*)ret)->exit_result << endl;delete iter;}cout << "main thread quit " << endl;return 0;
}

线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

int pthread_detach(pthread_t thread);

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

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

在这里插入图片描述
在这里插入图片描述
处理线程分离有两种方式

  1. 主线程获取到新线程的线程标识符后分离 pthread_detach(tid);
  2. 新线程获取到自身的线程标识符后分离 pthread_detach(pthread_self());
    但是新线程和主线程的运行顺序是不可知的(由CPU调度器决定),所以可能新线程还没进行线程分离,主线程就进入阻塞等待了
    所以推荐让主线程对新线程做分离操作

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

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

pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质
就是一个进程地址空间上的一个地址。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

线程的局部存储

在这里插入图片描述
为啥g_val的地址值变化很大呢?
一开始全局变量是在已初始化数据段,所以地址很小
但是变成线程局部存储时,就位于共享区,地址变大很多
(地址由低到高)

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

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

相关文章

鸿蒙开发接口UI界面:【@ohos.router (页面路由)】

页面路由 说明开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。页面路由需要在页面渲染完…

AI与空间设计的碰撞?

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答9 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

探索python数据可视化的奥秘:打造专业绘图环境

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、搭建专业绘图环境 二、掌握绘图基本原理 三、解锁绘图高级技巧 四、总结与展望 在数据…

比较(一)利用python绘制条形图

比较&#xff08;一&#xff09;利用python绘制条形图 条形图&#xff08;Barplot&#xff09;简介 条形图主要用来比较不同类别间的数据差异&#xff0c;一条轴表示类别&#xff0c;另一条则表示对应的数值度量。 快速绘制 基于seaborn import seaborn as sns import matplo…

banner2.0自定义轮播布局

说明&#xff1a;最近碰到一个需求&#xff0c;让新闻列表实现轮播图的效果&#xff0c;也就是轮播新闻&#xff0c;然后样式必须按照ui设计的样式来弄&#xff0c;之前传统的banner&#xff0c;都是只轮播图片&#xff0c;没想到&#xff0c;这次居然要轮播新闻&#xff0c; 网…

MySQL 重启之后无法写入数据了?

数据库交接后因 persist_only 级别的参数设置引发的故障分析。 作者&#xff1a;不吃芫荽&#xff0c;爱可生华东交付服务部 DBA 成员&#xff0c;主要负责 MySQL 故障处理及相关技术支持。 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系…

CentOS配置DNS

1.打开/etc/resolv.conf文件 sudo vi /etc/resolv.conf2.添加配置 nameserver 114.114.114.1143.保存并关闭文件。 4.为了确保配置生效&#xff0c;重启网络服务或重启系统。例如&#xff1a; 重启网络&#xff1a; sudo systemctl restart network重启系统&#xff1a; …

【渗透测试】|基于dvwa的CSRF初级,中级,高级

一、渗透测试 二、渗透测试过程中遇到的问题和解决 在初级csrf中&#xff0c;想要通过伪造一个404页面&#xff0c;达到修改密码的效果 伪造404页面的html代码如下&#xff1a; <html> <head> </head> <body> <img src"http://192.xx.xx.xx/…

mono3D任务FCOS3D: Fully Convolutional One-Stage Monocular 3D Object Detection

数据 KITTI 在卡尔斯鲁厄采集的数据&#xff0c;包括雷达&#xff08;64线束激光雷达&#xff09;和摄像头&#xff08;灰色彩色&#xff09;。目标为pvb,场景包括农村、城市、高速。3D目标检测任务包含7481 训练图片和7518 测试图片包含80.256 标注目标。同时带有点云信息。…

C++之类(class)的三种访问修饰符(public、private、protected)----成员变量与函数权限

1、背景介绍 在C中&#xff0c;类&#xff08;class&#xff09;的三种访问修饰符&#xff08;access specifiers&#xff09;用于控制类的成员&#xff08;属性和方法&#xff09;的访问权限。这些修饰符决定了类成员在类的外部是否可以被访问。以下是这三种访问修饰符的详细…

深度学习-语言模型

深度学习-语言模型 统计语言模型神经网络语言模型语言模型的应用序列模型&#xff08;Sequence Model&#xff09;语言模型&#xff08;Language Model&#xff09;序列模型和语言模型的区别 语言模型&#xff08;Language Model&#xff09;是自然语言处理&#xff08;NLP&…

信息安全法规和标准

《全国人民代表大会常务委员会关于维护互联网安全的决定》规定&#xff0c;威胁互联网运行安全的行为&#xff1a;&#xff08;1&#xff09;侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统&#xff0c;&#xff08;2&#xff09;故意制作、传播计算机病毒等破坏性…

Java 中BigDecimal传到前端后精度丢失问题

1.用postman访问接口&#xff0c;返回的小数点精度正常 2.返回到页面里的&#xff0c;小数点丢失 3.解决办法&#xff0c;在字段上加注解 JsonFormat(shape JsonFormat.Shape.STRING) 或者 JsonSerialize(using ToStringSerializer.class) import com.fasterxml.jackson.a…

SpringJDBC

1.前言 Spring JDBC可以帮助开发者节省大量开发工作 自动去处理一些低级细节 比如&#xff1a;异常处理、打开和关闭资源(Connection、PreparedStatement、Statement、ResultSet) 需要下载的jar包&#xff1a; spring-jdbc(普通jar包、源码jar包)由于没有依赖其他的jar包 所以只…

Echarts 实现线条绘制

文章目录 需求分析 需求 用 Echarts 实现如下效果 分析

【优选算法】分治 {三分快排:三指针优化,随机选key,快速选择算法;归并排序:统计数组中的逆序对,统计数组中的翻转对;相关编程题解析}

一、经验总结 1.1 三分快排 优化一&#xff1a;三指针优化 之前学习的快速排序无法妥善处理相等或重复序列的排序问题&#xff08;有序且三数取中无效&#xff09;&#xff0c;使快速排序的效率无法达到最优。 为了解决重复序列的问题&#xff0c;我们将原先的双指针法&…

云计算-无服务器计算与AWS Lambda (Serverless Computing with AWS Lambda)

AWS Lambda 无服务器计算与AWS Lambda AWS Lambda支持无服务器计算&#xff0c;不需要任何预配置和管理&#xff0c;同时还能最大限度地降低成本。我们将看到如何创建一个简单的Lambda函数&#xff0c;以及如何将其与AWS事件映射。在现实生活中&#xff0c;任何托管在线的应用…

每天学点小知识:图床搭建 + CDN简介

前言&#xff1a; 本章内容帮你解决&#xff0c;本地图片不能分享到网上的问题。需要工具github JSDelivr 知识点 Q&#xff1a;什么是JSDelivr&#xff1f; JSDelivr是一个免费且公开的内容分发网络&#xff08;CDN&#xff09;&#xff0c;专门用于加速开源项目和静态网站…

构建php环境、安装、依赖、nginx配置、ab压力测试命令、添加php-fpm为系统服务

目录 php简介 官网php安装包 选择下载稳定版本 &#xff08;建议使用此版本&#xff0c;文章以此版本为例&#xff09; 安装php解析环境 准备工作 安装依赖 zlib-devel 和 libxml2-devel包。 安装扩展工具库 安装 libmcrypt 安装 mhash 安装mcrypt 安装php 选项含…

2024年软件设计师备考复习资料(应用技术)

应用设计&#xff0c;考试时间为120分钟&#xff1b;总共需做5道题&#xff0c;满分75分&#xff08;每题15分&#xff09;。前4题为必答题&#xff0c;最后2题为要求选答一题&#xff08;C或Java&#xff09;&#xff0c;45及格 目录 1. 数据流图&#xff08;需求分析&#…