【Linux】22. 线程控制

news2024/11/16 17:44:43

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;
    // tid
    pthread_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 10
    for (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;
    // tid
    pthread_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 10
    for (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.coloradmin.cn/o/1711335.html

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

相关文章

成都爱尔眼科蔡裕主任解说什么是近视性黄斑病变

近视性黄斑病变&#xff0c;属于黄斑病变的其中一种。 黄斑是眼内一个部位&#xff0c;它位于眼底的后极部&#xff0c;视网膜的中心部&#xff0c;管理着光、形、色。黄斑变性是指由于年龄、遗传、不良环境、慢性光损伤等各种因素的影响&#xff0c;使眼部视网膜处的黄斑发生…

kafka-主题创建(主题操作的命令)

文章目录 1、topic主题操作的命令1.1、创建一个3分区1副本的主题1.1.1、获取 kafka-topics.sh 的帮助信息1.1.2、副本因子设置不能超过集群中broker的数量1.1.3、创建一个3分区1副本的主题1.1.4、查看所有主题1.1.5、查看主题详细描述 1、topic主题操作的命令 kafka发送消息会存…

彩光赋能中国智造 极简光3.X助力“数智”转型

蒸汽时代、电气时代、信息时代三大工业革命后 互联网和智能制造主导的工业4.0时代来临 大数据、云计算、人工智能等新兴技术 对企业园区的网络架构、负载能力等 提出了新要求,也使得光纤较于传统铜缆 在距离、性能、延时上的优势日益凸显 基于此 围绕未来园区网建设的企…

【NumPy】深入了解NumPy的multiply函数:高效矩阵和数组乘法指南

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

positivessl泛域名证书500元13个月

随着创建网站的门槛变低&#xff0c;不论是个人用户还是企事业单位用户创建的域名网站也越来越多&#xff0c;怎么维护网络环境的安全成为了各个用户需要解决的问题。为了保护网站的数据安全&#xff0c;防止恶意攻击和数据泄露&#xff0c;大多数用户选择为域名网站安装数字证…

基于51单片机的直流电机调速设计

一.硬件方案 本系统采用STC89C51控制输出数据&#xff0c;由单片机IO口产生PWM信号&#xff0c;送到直流电机&#xff0c;直流电机通过测速电路将实时转速送回单片机&#xff0c;进行转速显示&#xff0c;从而实现对电机速度和转向的控制&#xff0c;达到直流电机调速的目的。…

excel 点击单元格的内容 跳转到其他sheet设置

如图点击1处跳转到2 按照如下图步骤操作即可

【机器学习聚类算法实战-5】机器学习聚类算法之DBSCAN聚类、K均值聚类算法、分层聚类和不同度量的聚集聚类实例分析

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

补环境——A股市场

补环境 吐环境 1.Proxy对象 Proxy对象由两个部分组成&#xff1a;target、handler target:目标对象 handler&#xff1a;是一个对象&#xff0c;声明了代理target的指定行为&#xff0c;支持的拦截操作&#xff0c;一共13种&#xff1a; get(target,propKey,receiver)&…

DataGrip使用ssh连接数据库的操作流程

1 选择数据源种类 2 配置ssh 3 填写host、port和认证方式 我选择的密码方式&#xff0c;也可选择其他方式连接&#xff1a; 本文由博客一文多发平台 OpenWrite 发布&#xff01;

内网安全--隧道技术-MSF上线本地

免责声明:本文仅做技术交流与学习... 不得不说,小白最近也是用上了viper,这里要特别感谢一下my bro 北岭敲键盘的荒漠猫 MSF--viper: --生成马子-->上线 --进入meterpreter. 1-查看路由,添加路由. 查看路由信息 : run autoroute -p run post/multi/manage/autoroute 添加…

PostgreSQL发展史

PostgreSQL是一个开源的对象-关系型数据库管理系统&#xff08;ORDBMS&#xff09;&#xff0c;其历史可以追溯到上世纪80年代。以下是对PostgreSQL发展史的深入解析&#xff1a; 1980年代&#xff1a;起源 1.Ingres 项目 1977年&#xff0c;Michael Stonebraker 和他的团队…

数字信号处理实验三:IIR数字滤波器设计及软件实现

一、实验目的 1. 掌握MATLAB中进行IIR模拟滤波器的设计的相关函数的应用&#xff1b; 2. 掌握MATLAB的工具箱中提供的常用IIR数字滤波器的设计函数的应用&#xff1b; 3.掌握MATLAB的工具箱中提供的模拟滤波器转数字滤波器的相关的设计函数的应用。 二、实验内容 本实验为…

INDICATOR 再c嵌入sql环境中的作用

在C语言嵌入SQL&#xff08;Embedded SQL&#xff09;的环境中&#xff0c;INDICATOR关键字用于处理数据库中的NULL值&#xff0c;以及管理与之相关联的宿主变量&#xff08;host variables&#xff09;。具体来说&#xff0c;它的作用主要体现在以下几个方面&#xff1a; NUL…

海外仓ERP系统:赋能海外仓,实现标准化管理

随着业务规模的不断发展和业务类型的复杂度逐渐提升&#xff0c;传统的海外仓管理模式已经很难适应现在的情况了。对海外仓企业来说&#xff0c;一套合适的海外仓管理erp系统可以起到很大的辅助作用。 不过很多小型海外仓企业会纠结于是同时选择企业erp系统和海外仓管理系统&a…

java的方法重写

重写的概述 重写是基于继承来说的&#xff0c;因为父类的方法需求不满足于子类&#xff0c;所以就要在进行方法重写&#xff0c;如果不知道继承是啥可以看我上一篇笔记 在这里用代码举个栗子 例如&#xff1a;我们定义了一个动物类代码如下&#xff1a; public class Animal…

地下停车场FM信号覆盖系统技术原理用与应用

随着我国城市化水平的快速推进与房地产的快速发展&#xff0c;城市停车场称为每栋建筑物的硬性配套建筑&#xff0c;尤其是商业综合体、医院、政府机关、机场、高铁站等场所出现了超大规模停车场&#xff0c;停放车辆可达数千辆&#xff0c;停车场的智能化与信息化水平也越来越…

实时合成 1 秒频订单簿快照:DolphinDB INSIGHT 行情插件与订单簿引擎应用

INSIGHT 是华泰证券依托大数据存储、实时分析等领域的技术积累&#xff0c;整合接入国内多家交易所高频行情数据&#xff0c;为投资者提供集行情接入、推送、回测、计算及分析等功能于一体的行情数据服务解决方案。基于 INSIGHT 官方提供的行情数据服务 C SDK&#xff08;TCP 版…

二叉树的链式结构(二叉树)与顺序结构(堆)---数据结构

一、树的概念与结构 1、树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。我们常把它叫做树&#xff0c;是因为它看起来像一棵倒挂的树&#xff0c;它的根是朝上的&#xff0c;而叶是朝下的。 下面…