虎先锋,你也喜欢线程控制嘛

news2025/1/20 1:02:38

讲讲线程控制捏

线程创建

这是创建线程调用的接口:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

这个接口上一篇文章已经介绍过了

线程等待

那么我们来看看下一个等待的接口:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

 第一个参数是你要等哪个线程,就把哪个线程的ID输进去,第二个参数设为nullptr就好(不关心它的返回值的话)

然后就是写一段代码:

#include <iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadRun(void* args)
{

}

int main()
{
    pthread_t tid;      //线程的ID
    int n = pthread_create(&tid,nullptr,threadRun,(void*)"thread 1");
    if(n != 0)
    {
        std::cerr << "create thread error" << std::endl;
        return 1;
    }
    return 0;
}

问题来了:main和new线程哪个先运行?

答案是:不确定。 

和进程那套一样,就是不确定

第二个问题:我们期望谁最后退出?

在进程阶段,我们希望父进程后退出

因为我们希望父进程给紫禁城擦屁股

同样的,放到线程这里,我们希望main thread能提前退出

所以在我们的代码里放上线程等待的部分:

#include <iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadRun(void* args)
{

}

int main()
{
    pthread_t tid;      //线程的ID
    int n = pthread_create(&tid,nullptr,threadRun,(void*)"thread 1");
    if(n != 0)
    {
        std::cerr << "create thread error" << std::endl;
        return 1;
    }
    n = pthread_join(tid,nullptr);
    if(n == 0)
    {
        std::cout << "main thread wait success" << std::endl;
    }
    return 0;
}

如果不join,那么主线程退出,新线程也就跟着退掉了,容易造成僵尸进程的问题

我们来验证一下这个join:

#include <iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadRun(void* args)
{
    int cnt = 5;
    while (cnt)
    {
        std::cout << "new thread run ...,cnt : " << cnt-- << std::endl;
        sleep(1);
    }
    return nullptr;    
}

int main()
{
    pthread_t tid;      //线程的ID
    int n = pthread_create(&tid,nullptr,threadRun,(void*)"thread 1");
    if(n != 0)
    {
        std::cerr << "create thread error" << std::endl;
        return 1;
    }
    n = pthread_join(tid,nullptr);
    std::cout << "main thread join begin..." << std::endl;
    if(n == 0)
    {
        std::cout << "main thread wait success" << std::endl;
    }
    return 0;
}
while :; do ps -aL;sleep 1;done

 这个线程id是一个虚拟地址(tid)

那么我们怎么看待线程传参的行为呢?

我们可以传递各种类型的参数,但是自己要记得传了啥(也可以传递类对象的地址):

#include <iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

class ThreadData
{
public:
    std::string name;
    std::string num;
};

void* threadRun(void* args)
{
    int cnt = 5;
    ThreadData *td = static_cast<ThreadData*>(args);
    while (cnt)
    {
        std::cout << "new thread run ...,cnt : " << cnt-- << std::endl;
        sleep(1);
    }
    return nullptr;    
}

int main()
{
    pthread_t tid;      //线程的ID
    ThreadData td;
    td.name = "thread-1";
    td.num = 1;
    int n = pthread_create(&tid,nullptr,threadRun,(void*)&td);
    if(n != 0)
    {
        std::cerr << "create thread error" << std::endl;
        return 1;
    }
    n = pthread_join(tid,nullptr);
    std::cout << "main thread join begin..." << std::endl;
    if(n == 0)
    {
        std::cout << "main thread wait success" << std::endl;
    }
    return 0;
}

static_cast是安全类别的强转,对目标对象会做安装性检查

我们刚才是让新线程访问主线程栈上的变量,它有点破坏主线程的完整性和独立性了,而且如果要是两个线程同时访问,更容易出问题

我们建议是申请一块空间:

#include <iostream>
#include<string>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

class ThreadData
{
public:
    std::string name;
    std::string num;
};

void* threadRun(void* args)
{
    int cnt = 5;
    ThreadData *td = static_cast<ThreadData*>(args);
    while (cnt)
    {
        std::cout << "new thread run ...,cnt : " << cnt-- << std::endl;
        sleep(1);
    }
    return nullptr;    
}

int main()
{
    pthread_t tid;      //线程的ID
    ThreadData *td = new ThreadData();
    td->name = "thread-1";
    td->num = 1;
    int n = pthread_create(&tid,nullptr,threadRun,td);
    if(n != 0)
    {
        std::cerr << "create thread error" << std::endl;
        return 1;
    }
    n = pthread_join(tid,nullptr);
    std::cout << "main thread join begin..." << std::endl;
    if(n == 0)
    {
        std::cout << "main thread wait success" << std::endl;
    }
    return 0;
}

人手一个堆空间,多线程就不会相互干扰了

邮专,你让我们人手一个上床下桌,光电孙和计科爷就不会相互干扰了

柚专,你让我们人手一个通行码,首陀罗和婆罗门就不会相互干扰了

传参问题搞定了,是时候来看看返回值是怎么会事了

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

这个retval是一个输出型参数

 因为线程退出时的返回值是void*,所以想要获取新线程退出时的返回值就需要传void**

void *code = nullptr;           //开辟了空间
n = pthread_join(tid,&code);

这样就可以获取新线程的退出信息,通过退出信息可以得知线程的任务完成情况

那么我们应该怎么看待线程的函数返回呢?

事实证明,我们只需要考虑正确的返回,不考虑异常,因为异常了的话整个程序就崩溃了,包括主线程

我们不仅可以返回数字,还可以返回结构体对象

但是我们截至目前还只是创建了一个线程,怎样创建多线程呢?

因为线程创建的时候需要有自己的ID,所以我们如果想要优雅的创建则需要用到vector

#include <iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadrun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        std::cout << name << "is running" << std::endl;
        sleep(1);
    }
    
}

const int num = 10;

int main()
{
    std::vector<pthread_t>tids;
    for(int i=0;i<num;i++)
    {
        //有线程的ID
        pthread_t tid;
        char name[128];
        snprintf(name,sizeof(name),"thread-%d",i+1);

        pthread_create(&tid,nullptr,threadrun,name);
    }
    sleep(100);
    return 0;
}

但是这个代码很奇怪

 

因为线程都去执行这个, 线程创建是那样创建的,但是谁先调度是不确定的

而因为这个name是在主线程的栈空间上的,所以这么多线程都去访问就容易出现数据问题

所以我们需要改变一下我们的code:

#include <iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadrun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        std::cout << name << "is running" << std::endl;
        sleep(1);
    }
    
}

const int num = 10;

int main()
{
    std::vector<pthread_t>tids;
    for(int i=0;i<num;i++)
    {
        //有线程的ID
        pthread_t tid;
        char *name = new char[128];
        snprintf(name,128,"thread-%d",i+1);

        pthread_create(&tid,nullptr,threadrun,name);
    }
    sleep(100);
    return 0;
}

 为了方便管理,我们要保存所有的线程的ID信息

#include <iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadrun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        std::cout << name << "is running" << std::endl;
        sleep(1);
        break;
    }
    return nullptr;
}

const int num = 10;

int main()
{
    std::vector<pthread_t>tids;
    for(int i=0;i<num;i++)
    {
        //有线程的ID
        pthread_t tid;
        char *name = new char[128];
        snprintf(name,128,"thread-%d",i+1);

        pthread_create(&tid,nullptr,threadrun,name);
        tids.emplace_back(tid);
    }

    for(auto tid : tids)
    {
        pthread_join(tid,nullptr);
    }
    return 0;
}

如果加上线程的等待的话就是:

#include <iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include <unistd.h>
#include <ctime>

void* threadrun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        std::cout << name << " is running" << std::endl;
        sleep(1);
        break;
    }
    return args;
}

const int num = 10;

int main()
{
    std::vector<pthread_t>tids;
    for(int i=0;i<num;i++)
    {
        //有线程的ID
        pthread_t tid;
        char *name = new char[128];
        snprintf(name,128,"thread-%d",i+1);

        pthread_create(&tid,nullptr,threadrun,name);
        tids.push_back(tid);
    }

    for(auto tid : tids)
    {
        void *name = nullptr;
        pthread_join(tid,&name);
        std::cout << (const char*)name << " quit... " << std::endl;
        delete (const char*)name;
    }
    return 0;
}

 

线程终止

接下来来看看线程如何终止吧

如果main函数结束,那么也代表着main thread结束,而mainthread结束也代表着进程结束了

所以我们得保证主线程退出的时候其他线程差不多跑完了

return和exit有区别,调用exit表示进程终止,而return可以用来终止线程

而想要线程结束可以调用一个接口:

#include <pthread.h>
void pthread_exit(void *retval);

这是控制线程退出 

void* threadrun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while (true)
    {
        std::cout << name << " is running" << std::endl;
        sleep(1);
        break;
    }
    pthread_exit(args);
}

#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.

取消线程需要一个条件:线程存在(不存在取消集贸啊)

线程被取消之后的退出结果是-1

线程分离

 我们可不可以不join线程,让它自己执行完之后就退出啊?

答案肯定是可以的,那么我们应该怎么做呢?

引入一个函数:

#include <pthread.h>
int pthread_detach(pthread_t thread);

炫酷的,,,线程分离!!!

当一个线程被创建,那么它默认是joinable的,是必须要join的

如果一个线程被分离,那么它不能也不需要被join

 线程可以自己分离自己,也可以主线程分离新线程,只要线程存在即可

分离之后的线程相当于跟所有人断绝关系,自生自灭

C嘎嘎也有线程库,那我为什么还要用里牛渴死的原生线程库?

学长:这不叫里牛渴死

 

#include <iostream>
#include<string>
#include<vector>
#include<pthread.h>
#include<thread>
#include<stdlib.h>
#include <unistd.h>

void threadRun(std::string name,int num)
{
    while (num)
    {
        std::cout << name << " num: " << num << std::endl;
        num--;
        sleep(1);
    }
    
}

int main()
{
    std::string name = "thread-1";
    std::thread mythread(threadRun, name ,10);
    while (true)
    {
        std::cout << "main thread..." << std::endl;
    }
    
    mythread.join();
    return 0;
}

C++内部封装的多线程的东西在编译的时候也要加线程库,否则编不过(C++11线程库的本质就是对原生线程库接口的封装)

当年说文件的时候也是这样的

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

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

相关文章

什么是“云原生”

什么是“云原生” K8s已经成为一线大厂分布式平台的标配技术 CNCF&#xff0c;全称为Cloud Native Computing Foundation&#xff0c;中文译为“云原生计算基金会” CNCF是云原生领域影响力最大最有话语权的组织 云原生技术有利于各组织在公有云、私有云和混合云等新型动态…

Flask 实现用户登录功能的完整示例:前端与后端整合(附Demo)

目录 前言Demo 前言 对于python用户的登录&#xff0c;以下只是提供一个Demo用于学习 更多的python知识点可从我的专栏中进行学习 python专栏详细分析Flask中的蓝图Blueprint&#xff08;附Demo&#xff09;详细分析Flask部署云服务器&#xff08;图文介绍&#xff09;构建F…

HarmonyOS开发实战(5.0)实现二楼上划进入首页效果详解

鸿蒙HarmonyOS开发实战往期必看文章&#xff1a; HarmonyOS NEXT应用开发性能实践总结 一分钟了解”纯血版&#xff01;鸿蒙HarmonyOS Next应用开发&#xff01; 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!

完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 亲测有效 完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01;…

算法-深度拷贝链表(138)

深度拷贝一个链表可以分以下几个步骤&#xff1a; 步骤 1&#xff1a;插入新节点 目标&#xff1a;在每个节点后面插入一个复制的节点。步骤&#xff1a; 遍历整个链表。对于每个节点 current&#xff0c;创建一个新节点 newNode&#xff0c;其值为 current.val。将 newNode …

深入探讨IDSIPS:信息安全的未来趋势与应用

引言 在信息技术飞速发展的今天&#xff0c;网络安全问题愈发突出。随着数据泄露、网络攻击等事件频发&#xff0c;企业和个人对信息安全的重视程度不断提高。IDSIPS&#xff08;Intrusion Detection System and Intrusion Prevention System&#xff09;作为信息安全领域的重…

在Spring项目中,两个实用的工具(生成类与映射文件、API自动生成)

尊贵的Spring玩家&#xff0c;是不允许动脑思考的&#xff0c;所以我们要学会复制粘贴 1.生成类与映射文件 背景&#xff1a;在项目编写初期&#xff0c;我们已经设计好了表&#xff0c;后面就需要根据表来撰写实体类(model)和对应的sql语句(dao和mapper)。如果一个项目中&…

【动态规划】两个数组的 dp 问题一

两个数组的 dp 问题 1.最长公共子序列2.不相交的线3.不同的子序列4.通配符匹配 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.最长公共子序…

深度学习水印网络架构学习笔记

目前学习到的一些网络架构&#xff0c;简单整理如下。 1、END框架【嵌入器-噪声层-提取器】 HiDDeN: Hiding Data With Deep Networks. ECCV, 2018.END框架&#xff0c;对噪声层的设计。用可导操作模拟JPEG压缩的过程。 2、噪声层图像增强【Noise Layer】 MBRS: Enhancing R…

设计模式之外观设计模式

一、外观设计模式概念 外观模式 (Facade) 是一种结构型设计模式&#xff0c; 为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 外观模式为复杂子系统提供了一个简单接口&#xff0c;并不为子系统…

[Python]案例驱动最佳入门:Python数据可视化在气候研究中的应用

在全球气候问题日益受到关注的今天&#xff0c;气温变化成为了科学家、政府、公众讨论的热门话题。然而&#xff0c;全球气温究竟是如何变化的&#xff1f;我们能通过数据洞察到哪些趋势&#xff1f;本文将通过真实模拟的气温数据&#xff0c;结合Python数据分析和可视化技术&a…

鸿蒙HarmonyOS开发:一次开发,多端部署(界面级)天气应用案例

文章目录 一、布局简介二、典型布局场景三、侧边栏 SideBarContainer1、子组件2、属性3、事件 四、案例 天气应用1、UX设计2、实现分析3、主页整体实现4、具体代码 五、运行效果 一、布局简介 布局可以分为自适应布局和响应式布局&#xff0c;二者的介绍如下表所示。 名称简介…

828华为云征文|华为云Flexus X实例docker部署最新Appsmith社区版,搭建自己的低代码平台

828华为云征文&#xff5c;华为云Flexus X实例docker部署最新Appsmith社区版&#xff0c;搭建自己的低代码平台 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Ng…

SQL优化-MySQL Explain中出现Select tables optimized away

文章目录 前言相关解释总结 前言 今天在做SQL优化的时候&#xff0c;在使用explain执行SQL时&#xff0c;出现了以下情况&#xff1a; EXPLAIN SELECT m1.id from station m1 INNER JOIN site s ON m1.codes.stationcode where receivetime(SELECT MAX(m2.receivetime) FROM…

基于Tesseract_OCR识别

1、安装Tesseract Mac版本&#xff0c;通过Homebrew进行安装即可brew install tesseract windows版本安装 下载地址&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 2、更换语言包 下载语言包 https://github.com/tesseract-ocr/tesseract 亦可参照这个 Tessera…

【CTF Reverse】XCTF GFSJ1101 Mine- Writeup(反编译+动态调试+Base58编码)

Mine- 运气怎么这么差&#xff1f; 原理 Base58 Base58是用于比特币&#xff08;Bitcoin&#xff09;中使用的一种独特的编码方式&#xff0c;主要用于产生Bitcoin的钱包地址。 相比Base64&#xff0c;Base58不使用数字"0"&#xff0c;字母大写"O"&…

Linux 文件权限详解与管理

文章目录 前言一、文件权限概述1. 权限表示格式2. 权限组合值 二、查看文件权限三、修改文件所有者与所属组1. 使用 chown 修改文件所有者2. 使用 chgrp 修改文件所属组3. 添加所有者 四、修改文件权限1. 符号方式2. 八进制方式 总结 前言 在 Linux 系统中&#xff0c;文件权限…

React + Vite 多环境配置

1.根目录创建文件&#xff1a; .env.dev //测试环境 .env.development //本地环境 .env.production //正式环境 .env.uat //预发布环境 注&#xff1a;变量名必须使用 VITE_API 开头 2.package.json 配置&#xff1a; --mode 设置读取制定 .env文件 &#xff0c;默认读取.en…

Windows10安装cuda11.3.0+cudnn8.5.0,以及创建conda虚拟环境(pytorch)

1、检查电脑驱动版本为561.09&#xff0c;选择cuda版本&#xff0c;下图可知cuda版本<12.6。 nvidia-smi #查看驱动版本&#xff0c;以及最大可以安装的cuda版本 2、Anaconda3-2024.06-1-Windows-x86_64.exe下载&#xff1a; 官网&#xff1a;https://www.baidu.com/link?…

研究生存指南:必备Zotero插件,让你的文献管理更轻松

在读研阶段&#xff0c;我经常面临大量文献阅读和项目研究的任务。忽略文献整理会导致后续使用时非常不便&#xff0c;查找困难且混乱。导师向我们推荐了 Zotero&#xff0c;经过亲身试用&#xff0c;我发现它非常好用&#xff01;zotero有非常多的插件&#xff0c;能够一个就满…