Linux_线程控制

线程控制的相关接口

进程创建相关

之前我们已经认识到了pthread_create函数用来创建线程,这里不再赘述。

pthread_self函数

void* routine(void* args)
{
    std::cout  << "我是新线程..." << pthread_self() << std::endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    while(true)    sleep(1);
    return 0;
}

通过结果我们可以发现,得到了一个数字。实际上,使用 pthread_self 得到的这个数实际上是虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性;而真正的线程ID是通过 ps -aL | head -1 && ps -aL | grep mythread 命令得到的。

进程等待相关

为什么需要线程等待?

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间。
  • 若不等待,就会有内存泄露的问题。

pthread_join函数

参数说明

  • pthread_t thread:这是要等待的线程的标识符(ID),该标识符是由 pthread_create 函数返回的。
  • void **retval:这是一个指向 void * 指针的指针,用于接收被等待线程的返回值。

retval参数的可能取值(下面代码中都会体现):

  1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,retval所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。
  3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

如果成功,pthread_join 返回 0;如果失败,则返回错误码。

 ps. 如果需要退出的线程一直不退出,那么等待该线程的线程就会一直等待!

void* routine(void* args)
{
    std::cout  << "我是新线程..." << pthread_self() << std::endl;
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    
    // 等待
    int n = pthread_join(tid1, nullptr);
    if(n != 0)
        std::cerr << "join error: "<< n << ", " << strerror(n) << std::endl;
    std::cout << "join success!" << std::endl;

    while(true)    sleep(1);
    return 0;
}


我们将代码稍作修改

void* routine(void* args)
{
    while(true)
    {
        std::cout  << "我是新线程..." << toHex(pthread_self()) << std::endl;
        sleep(1);
        break;
    }
    // 返回值:可以是变量、数字、对象!
    return (void*)10;    // 将返回结果修改为10
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");

    void *ret = nullptr;
    int n = pthread_join(tid, &ret);
    // 等待成功并打印ret的值
    std::cout << "join success!, ret: " << (long long int)ret << std::endl;
    return 0;
}

结论:ret的值其实就是routine的返回值!

ps. 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!栈空间也是如此!

Demo代码

// 创建多线程并等待的样例(传递参数为对象)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>

#define NUM 5

class ThreadDate
{
public:
    ThreadDate(){}
    void Init(int a, int b, std::string name)
    {   
        _a = a;
        _b = b;
        _name = name;
    }
    int Result()    {return _a + _b; }
    std::string Name(){return _name;}
    void SetId(pthread_t id){ _tid = id;}
    pthread_t Id(){return _tid;}
    int A(){return _a;}
    int B(){return _b;}
    ~ThreadDate(){}
private:
    int _a;
    int _b;
    int _result;
    std::string _name;
    pthread_t _tid;
};

void* routine(void* args)
{
    ThreadDate* td = static_cast<ThreadDate*>(args);

    std::cout << "我是新线程,我的名字是: " << td->Name() << std::endl;
    return nullptr;
}
int main()
{
    ThreadDate td[NUM];
    // 预处理
    for(int i = 0;i < NUM;i++)
    {
        char id[64];
        snprintf(id, sizeof(id), "thread-%d", i);
        td[i].Init(i * 10, i * 20, id);
    }
    // 创建多个线程
    for(int i = 0;i < NUM;i++)
    {
        pthread_t tid;
        pthread_create(&tid, nullptr, routine, &td[i]);
        td[i].SetId(tid);
    }
    // 等待多个线程
    for(int i = 0;i < NUM;i++)
    {
        pthread_join(td[i].Id(), nullptr);
    }
    // 汇总处理结果
    for(int i =0;i < NUM; i++)
    {
        printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
    }
    return 0;
}

进程终止模块

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

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

pthread_exit函数

 参数:

  • retval:这是一个指向任意数据的指针,该数据将被线程的终止状态所使用,并且可以被其他线程通过调用 pthread_join 来访问。
void* routine(void* args)
{
    std::string name = static_cast<const char*>(args);
    std::cout << "我是新线程..." << std::endl;
    pthread_exit((void*)10);    // 作用与return相同
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");

    void* ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "new thread exit code: " << (long long int)ret << std::endl;
    return 0;
}

pthread_cancel函数

参数:

  • thread:要发送取消请求的线程标识符(pthread_t 类型)。
void* routine(void* args)
{
    std::string name = static_cast<const char*>(args);
    while(true){
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }//该线程没有退出
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    
    sleep(2);
    pthread_cancel(tid);
    std::cout << "取消线程: " << tid << std::endl;
    sleep(2);

    void* ret = nullptr;
    // 取消之后的线程的返回值为:-1:PTHREAD_CANCELED ((void *) -1)
    // 所以取消之后的线程也必须join,否则就会内存泄漏
    pthread_join(tid, &ret);    
    std::cout << "new thread exit code: " << (long long int)ret << std::endl;
    return 0;
}

线程分离模块

pthread_detach函数

void* routine(void* args)
{
    // 线程分离可以自己主动分离
    // pthread_detach(pthread_self());
    std::string name = static_cast<const char*>(args);
    while(true){
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, routine, (void*)"thread");
    // 线程分离也可以被主线程分离
    pthread_detach(tid);

    sleep(2);    //先让线程分离,在进行等待

    void* ret = nullptr;
    int n = pthread_join(tid, &ret);
    // pthread_join返回0代表等待成功,非0为失败
    std::cout << "new thread exit code: " << (long long int)ret << ", n: " << n <<  std::endl;
    return 0;
}

线程分离之后,pthread_join就不会阻塞等待被分离的线程,所以join就会失败。

线程局部存储

线程局部存储(Thread Local Storage,TLS)是一种特殊的存储机制,用于为每个线程提供独立的变量副本,确保线程之间的数据隔离。

__thread关键字实现线程局部存储

__thread int tls_variable = 0; // 每个线程都有独立的tls_variable副本

ps. __thread只能修饰内置类型 

优点:

  • 线程安全:每个线程访问自己的变量副本,避免了线程之间的数据竞争。

  • 性能优化:减少了锁的使用,提高了多线程程序的性能。

  • 灵活性:可以存储线程相关的上下文信息。

缺点:

  • 内存占用:每个线程都会有一个独立的变量副本,可能会增加内存占用。

  • 线程生命周期管理:需要确保线程结束时清理线程局部存储的资源,否则可能导致内存泄漏。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用双指针一次遍历实现”找到“并”删除“单链表倒数第K个节点(力扣题目为例)

Problem: 19. 删除链表的倒数第 N 个结点 文章目录 题目描述思路复杂度Code 题目描述 思路 1.欲找到倒数第k个节点&#xff0c;即是找到正数的第n-k1、其中n为单链表中节点的个数个节点。 2.为实现只遍历一次单链表&#xff0c;我们先可以使一个指针p1指向链表头部再让其先走k步…

Ubuntu-手动安装 SBT

文章目录 前言Ubuntu-手动安装 SBT1. SBT是什么?1.1. SBT 的特点1.2. SBT 的基本功能1.3. SBT 的常用命令 2. 安装2.1. 下载2.2. 解压 sbt 二进制包2.3. 确认 sbt 可执行文件的位置2.4. 设置执行权限2.5. 创建符号链接2.6. 更新 PATH 环境变量2.7. 验证 sbt 安装 前言 如果您觉…

【ProtoBuf 安装】ProtoBuf在window/Linux下的安装 创建/删除swap分区

文章目录 1.ProtoBuf在window下的安装2.ProtoBuf在Linux下的安装创建swap分区命令解析关闭swap分区删除swap分区的影响 1.ProtoBuf在window下的安装 1、下载ProtoBuf编译器 下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 如果要在 C 下使用 Pro…

BAHD酰基转移酶对紫草素的手性催化-文献精读105

Two BAHD Acyltransferases Catalyze the Last Step in the Shikonin/Alkannin Biosynthetic Pathway 两个BAHD酰基转移酶催化了紫草素/左旋紫草素生物合成途径中的最后一步 一个BAHD酰基转移酶专门催化紫草素的酰基化&#xff0c;而另一个BAHD酰基转移酶则仅催化紫草素的对映…

C语言初阶力扣刷题——349. 两个数组的交集【难度:简单】

1. 题目描述 力扣在线OJ题目 给定两个数组&#xff0c;编写一个函数来计算它们的交集。 示例&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,2] 输出&#xff1a;[2] 输入&#xff1a;nums1 [4,9,5], nums2 [9,4,9,8,4] 输出&#xff1a;[9,4] 2. 思路 直接暴力…

在Docker 容器中安装 Oracle 19c

在 Docker 容器中安装 Oracle 19c 是可行的&#xff0c;但它相较于其他数据库&#xff08;如 MySQL、PostgreSQL 等&#xff09;会复杂一些&#xff0c;因为 Oracle 数据库有一些特定的要求&#xff0c;如操作系统和库的依赖&#xff0c;以及许可证问题。 不过&#xff0c;Ora…

WGCLOUD使用介绍 - 如何监控ActiveMQ和RabbitMQ

根据WGCLOUD官网的信息&#xff0c;目前没有针对ActiveMQ和RabbitMQ这两个组件专门做适配 不过可以使用WGCLOUD已经具备的通用监测模块&#xff1a;进程监测、端口监测或者日志监测、接口监测 来对这两个组件进行监控

初学stm32 --- FreeRTOS移植

目录 移植前准备 1. 基础工程 2. FreeRTOS 源码 添加 FreeRTOS 文件 1. 添加 FreeRTOS 源码 2. 将文件添加到工程 3. 添加头文件路径 4. 添加 FreeRTOSConfig.h 文件 (1) FreeRTOSConfig.h 获取途径一 (2) FreeRTOSConfig.h 获取途径二 (3) FreeRTOSConfig.h 获取途径…

ThreadLocal概述、解决SimpleDateFormat出现的异常、内存泄漏、弱引用、remove方法

①. ThreadLocal简介 ①. ThreadLocal是什么 ①. ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程…

【2024年 CSDN博客之星】我的2024年创作之旅:从C语言到人工智能,个人成长与突破的全景回顾

我的2024年创作之旅&#xff1a;从C语言到人工智能&#xff0c;个人成长与突破的全景回顾 引言 回望2024年&#xff0c;我不仅收获了技术上的成长&#xff0c;更收获了来自CSDN平台上无数粉丝、朋友以及网友们的支持与鼓励。在这条创作之路上&#xff0c;CSDN不仅是我展示技术成…

Windows11恢复传统右键菜单

Windows11恢复传统右键菜单 执行下面的命令(管理员下) reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /vetaskkill /f /im explorer.exestart explorer.exe或者 reg add "HKCU\Software\Classes\CLSID\{8…

PCIE模式配置

对于VU系列FPGA&#xff0c;当DMA/Bridge Subsystem for PCI Express IP配置为Bridge模式时&#xff0c;等同于K7系列中的AXI Memory Mapped To PCI Express IP。

WPS数据分析000008

目录 一、替换 通配符 求出橙色底纹单元格的和 二、定位 拆分并填充内容 删除空行 一、替换 快捷键ctrlh 注意&#xff1a;限制数据区域。 若为单元格&#xff0c;表示选择整个工作表。 通配符 求出橙色底纹单元格的和 第一步&#xff1a;查找出橙色单元格&#xff0c;c…

Excel制作合同到期自动提醒!

大家好&#xff0c;我是小鱼。 今天分享一下如何利用Excel制作合同到期提醒表&#xff0c;实现Excel表格自动计算合同到期日和天数&#xff0c;根据合同状态和到期天数自动填充颜色提醒&#xff0c;超实用。先看一下效果&#xff0c;已经到期的合同会自动被填充为红色&#xf…

GestureDetector组件的功能与用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了ListView响应事件的内容,本章回中将介绍GestureDetector Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的GestureDetector是一个事件响应Widget,它可以响应双击事件&…

0 基础学运维:解锁 K8s 云计算运维工程师成长密码

前言&#xff1a;作为一个过来人&#xff0c;我曾站在技术的门槛之外&#xff0c;连电脑运行内存和内存空间都傻傻分不清&#xff0c;完完全全的零基础。但如今&#xff0c;我已成长为一名资深的k8s云计算运维工程师。回顾这段历程&#xff0c;我深知踏上这条技术之路的艰辛与不…

【Unity】 HTFramework框架(五十九)快速开发编辑器工具(Assembly Viewer + ILSpy)

更新日期&#xff1a;2025年1月23日。 Github源码&#xff1a;[点我获取源码] Gitee源码&#xff1a;[点我获取源码] 索引 开发编辑器工具MouseRayTarget焦点视角Collider线框Assembly Viewer搜索程序集ILSpy反编译程序集搜索GizmosElement类找到Gizmos菜单找到Gizmos窗口分析A…

飞牛NAS新增虚拟机功能,如果使用虚拟机网卡直通安装ikuai软路由(如何解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 飞牛NAS虚拟机安装爱快教程 📒🛠️ 前期准备🌐 网络要求💾 下载爱快镜像🚀 开始安装💻 开启IOMMU直通🌐 配置网络🚨 解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题➕ 创建虚拟机🎯 安装ikuai💻 进…

嵌入式蓝桥杯电子赛嵌入式(第14届国赛真题)总结

打开systic 生成工程编译查看是否有问题同时打开对应需要的文档 修改名称的要求 5.简单浏览赛题 选择题&#xff0c;跟单片机有关的可以查相关手册 答题顺序 先从显示开始看 1,2 所以先打开PA1的定时器这次选TIM2 从模式、TI2FP2二通道、内部时钟、1通道设为直接2通道设置…

C# volatile 使用详解

总目录 前言 在多线程编程中&#xff0c;确保线程之间的正确同步和可见性是一个关键挑战。C# 提供了多种机制来处理这些挑战&#xff0c;其中之一就是 volatile 关键字。它用于指示编译器和运行时环境不要对特定变量进行某些优化&#xff0c;以保证该变量的读写操作是线程安全…