Linux:多线程[2] 线程控制

了解:
Linux底层提供创建轻量级进程/进程的接口clone,通过选择是否共享资源创建。
vforkfork都调用的clone进行实现,vfork和父进程共享地址空间-轻量级进程。
库函数pthread_create调用的也是底层的clone

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

功能:创建一个新的线程
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;失败返回错误码;并不设置error错误码,因为错误码会针对整个进程

1.线程创建 pthread_create

class ThreadDate//定义一个类传给每个创建的新线程来命名区分它们
{
public:
    pthread_t tid;
    char namebuffer[64];
    int number;
};
void* thread_routine(void *args)//新线程调用的函数
{
    ThreadDate *td = static_cast<ThreadDate *>(args);//安全的进行强制类型转化
    int cnt = 10;
    while(cnt)
    {
        cout<<" new thread create success,name: "<<td->namebuffer<<" cnt:"<<cnt--<<endl;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    vector<ThreadDate*> threads;
#define NUM 10
    for(int i=0;i<NUM;i++)//主线程创建十个新线程
    { 
        ThreadDate *td = new ThreadDate();
        //char namebuffer[64];
        td->number = i+1;
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i+1);
        pthread_create(&td->tid,nullptr,thread_routine,td);//td是自定义类型,传参时会拷贝,
        threads.push_back(td);//创建成功时将每一个新线程信息保留
    }
    return 0;
}

传给新线程的变量定义在堆栈上的区别: 当我们要创建多个线程时,我们需要传参数void *arg来对每个线程进行命名来区分它们。如果我们定义char namebuffer[64];因为新线程与主线程的异步性;如果定义到了循环里,每次循环结束这个局部变量就已经被销毁了,但被创建出来的进程还保留着指向它的地址,那么这个地址中存的东西可能会被别的变量覆盖;如果是new一个自定义类的空间,是因为在堆上所以生命周期长不会被覆盖。

2.线程终止 pthread_exit / return


——退出信号:
为什么没有见到,线程退出的时候,对应的退出信号?? 线程出异常,收到信号,整个进程会退出!thread_join:默认就认为函数会调用成功!不考虑异常的问题,异常是进程要考虑的问题。
如果一个线程出了异常,会影响其它进程吗?-所有线程退出(健壮性或鲁棒性过差)。
一个线程出现野指针问题向所有PID为该值的线程都写入11号信号段错误;一个线程出错这个进程就发生异常,OS会回收该进程的资源,剩余的线程也不会活下去。
进程不会相互影响,因为进程有独立的资源。用exit()退出线程则会终止该线程所在的整个进程。

——返回值
pthread_exit(nullptr);
return nullptr; 线程的函数跑完了也就相当于该线程跑完了,用return也可以结束。
返回值的参数类型是void *retval,join传入void**类型指针即可拿到该返回值。此处也可以用自定义类的方式作为返回值,返回线程终止时的各种状态。

class ThreadReturn
{
public:
    int exit_code;
    int exit_result;
};
void* thread_routine(void *args)//可重入状态--是可重入函数
{
    //... ...
    ThreadReturn *tr = new ThreadReturn();
    //ThreadReturn tr;//在栈上定义,牵扯到会被释放的问题return (void*)&tr;
    tr->exit_code = 1;
    tr->exit_result = 106;
    return (void*)tr;//右值
    //pthread_exit((void*)tr);
}
int main()
{
    //... ...
    for(auto &iter : threads)
    {
        ThreadReturn *ret = nullptr;
        int n = pthread_join(iter->tid,(void**)&ret);//void ** retp;*retp,从库中获取指定线程的输出结果
        assert(n==0);
        cout<<"jion :"<<iter->namebuffer<<"success,exit_code: "<<ret->exit_code<<",exit_result: "<<ret->exit_result<<endl;
        delete iter;
    } 
    cout<<"main thread quit"<<endl;
    return 0;
}

3.线程等待回收 pthread_join

线程退出时它的PCB也需要释放,如果不等待,会造成类似僵尸进程的问题(暂时无法查看),造成内存泄漏。<1.所以我们要获取新线程的退出信息,<2.回收新线程的内存资源,避免内存泄漏。

 vector<ThreadDate*> threads;
#define NUM 10
for(int i=0;i<NUM;i++)//主线程创建十个新线程
    { 
        ThreadDate *td = new ThreadDate();
        td->number = i+1;
        snprintf(td->namebuffer,sizeof(td->namebuffer),"%s:%d","thread",i+1);
        pthread_create(&td->tid,nullptr,thread_routine,td);//td是自定义类型,传参时会拷贝,
        threads.push_back(td);//创建成功时将每一个新线程信息保留
    }
for(auto &iter : threads)//主线程打印出它所创建的所有新线程
    {
        cout<<"create thread:"<<iter->namebuffer<<":"<<iter->tid<<"  suceess"<<endl;
    }
for(auto &iter : threads)//主线程回收创建出来的所有新线程
    {
        int n = pthread_join(iter->tid,nullptr);
        assert(n==0);
        cout<<"jion :"<<iter->namebuffer<<"success"<<endl;
        delete iter;
    }

线程返回值
为什么return和pthread_exit(nullpter)参数都是nullptr 
void **retval用来获取线程函数结束时,返回的退出结果!

4.线程取消pthread_cancel

//线程是可以被cancel取消的!注意:线程要被取消,前提是这个线程已经被运行起来了。
    for(int i=0;i<threads.size()/2;i++)//主线程打印出它所创建的所有新线程
    {
        pthread_cancel(threads[i]->tid);//PTHREAD_CANCELED -1
        cout<<"pthread_cancel :"<<threads[i]->namebuffer<<"success"<<endl;
    }
    for(auto &iter : threads)
    {
        void *ret = nullptr;//我们在thread_routine返回一个return (void*)100;
        int n = pthread_join(iter->tid,(void**)&ret);//void ** retp;*retp,从库中获取指定线程的输出结果
        assert(n==0);
        cout<<dec<<"jion :"<<iter->namebuffer<<"success,exit_code: "<<(long long)ret<<endl;
        delete iter;
    } 
    cout<<"main thread quit"<<endl;

实验结果显示:cancel取消的进程返回jion回收的结果返回的都是-1(宏PTHREAD_CANCELED);没被取消的进程被jion回收的值都是我们在thread_routine中设定的返回值return (void*)100;

5.补充:初步重新认识线程库(语言版)

任何语言在Linux中要实现多线程,必定要使用Pthread库,如何看待c++11中的多线程?c++11的多线程,在Linux环境中,本质是对pthread库的封装!
推荐使用C++,如果在纯Linux环境下推荐使用原生线程库。在Windows环境中,语言对库的封装解决了平台的差异性,可以跨平台。在主流的框架中直接使用的原生线程库。

#include<iostream>
#include<thread>
#include<unistd.h>
void thread_run()
{
    while(true)
    {
        std::cout<<"我是新进程..."<<std::endl;
        sleep(1);
    }
}
int main()
{
    std::thread t1(thread_run);
    while(true)
    {
        std::cout<<"我是主线程..."<<std::endl;
        sleep(1);
    }
    t1.join();
    return 0;
}

6.线程分离

线程是可以等待的,等待的时候使用jion—阻塞式,如果不等待就会造成PCB内存泄漏,类似于僵尸进程的问题。线程不存在非阻塞式等待,那如果不等待呢?
—默认情况下,新创建的线程是joinable的(jionable表示该线程必须被jion),线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
—如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。这种情况就被成为线程分离。

6.1pthread_self()接口,获取自己的线程ID
std::string changeId(const pthread_t &thread_id)//格式化一下传入的线程ID
{
    char tid[128];
    snprintf(tid,sizeof(tid),"0x%x",thread_id);
    return tid;
}
void *start_routine(void *args)
{
    pthread_detach();//1.join时还没分离
    std::string threadname = static_cast<const char*>(args);
    while(true)
    {
        std::cout<<threadname<<"running...:"<<changeId(pthread_self())<<std::endl;
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,start_routine,(void*)"thread 1");
    pthread_detach(tid);//2.确保在join之前分离
    std::string main_id = changeId(pthread_self());
    std::cout<<"main thread running...\n"<<"主线程ID:"<<main_id<<"新线程ID:"<<changeId(tid)<<std::endl;
    pthread_join(tid,nullptr);//回收tid的线程,不关心返回值传nullptr
}

6.2 pthread_detach分离一个线程(参数是线程ID)

主线程join的时候,新线程还没执行分离...;所以得在主函数的join之前,新线程创建之后进行分离。

7.补充:创建一个线程对应库和内核所做的工作:

操作系统对系统内的多进程,库要对线程做管理——先描述(线程的属性:tid/独立栈/...),再组织。在库中要创建线程的结构体,在库中用户级线程,用户关心的线程属性在库中,内核提供线程执行流的调度,Linux中用户级线程:内核轻量级进程=1:1。
用户级线程ID究竟是什么?
堆栈之间有一个共享区,库是一个磁盘文件(库文件),进程要访问一个库必须将它加载到内存映射到地址空间当中,通过地址空间和页表找到库对应的共享区,在库中每一个线程当被创建时,在内核中给它创建对应的线程ID,在库当中也要给它创建一个描述线程的结构体(TCB:)。所有线程的TCB都放在一个数组中,线程ID就是该线程在库中TCB的地址。 所以当一个线程终止时,它的返回值就通过它在库中的地址也就是线程ID放入TCB中,join时即可拿到。而每个线程的私有栈都在它们库中的TCB中,多个轻量级进程每个所对应的栈,主线程的栈是在地址空间中,其它线程的栈在线程库中。  底层创建轻量级进程是库调用clone创建的。
主线程调用库创建新线程,库先为新线程创建一个TCB,然后调用Linux提供创建轻量级进程的接口clone,将创建好的TCB对应的回调方法、私有栈、参数传递给clone,所以线程一旦创建好了就会依赖我们的原生线程库。

线程的局部存储
想给线程定义一些私有的属性,不想放在栈上或者malloc在堆上,就想这个线程在运行起来后就天然具有独立的空间,你就可以设置私有属性,这种局部存储就是介于全局变量和局部变量之间的线程特有的一种属性。

__thread int thread_specific_variable;

8.自定义实现原生线程的封装

.hpp里面既包括方法的声明也包括方法的定义,在使用时只要包括它即可。

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstring>
#include <functional>
#include <cassert>
class thread;
class context//上下文,当成一个大号的结构体——解决this指针的问题
{
public:
    Thread *this_;
    void *args_;
public:
    context()
    :this_(nullptr),args_(nullptr);
    {}
    ~context()
    {}
};

class thread
{
public:
    //using func_t = std::function<void*(void*)>;
    typedef std::function<void*(void*)> func_t;//C++的函数对象
    const int num = 1024;
public:
    Thread(func_t func,void *args,int number)//构造函数,初始化func和用number线程name
    :func_(func)
    ,args_(args)
    {
        char buffer[num];//c99支持
        //name_ = "thread_";
        //name_+=std::to_string(number);
        snprintf(buffer,sizeof buffer,"thread-%d",number);
        name_ = buffer;
    }
    //在类内创建线程,想让线程执行对应的方法,需要将方法设置成为static
    static void *start_routine(void *args)//类内成员,隐含this会和pthread_create参数不一致会有两个参数导致不一致
    {
        context *ctx = static_cast<context *>(args);
        void *ret = ctx->this_->run(ctx->args_);
        delete ctx;
        return ret;
        //静态方法不能调用成员方法和成员变量,只能调用静态成员方法和变量
        //return func_(args_);//复习类中的static修饰!!!!
    }
    void start()
    {
        context *ctx = new context();
        ctx->this_ = this;
        ctx->args_ = args_;
        int n = pthread_create(&tid_,nullptr,start_routine,ctx);//函数是C++规则,调用c式接口
        assert(n == 0);//编译debug的方式发布的时候存在,release方式发布,assert就不存在了,n就只被定义没使用
        (void)n;//意料之外用if/异常,意料之中用assert
    }
    void join()
    {
        int n = pthread_join(tid_,nullptr);
        assert(n==0);
        (void)n;
    }
    void *run(void *args)
    {
        func_(args);
    }
    ~Thread()
    {
        //do nothing
    }
private:
    std::string name_;
    func_t func_;//线程未来要运行时所要执行的函数
    pthread_t tid_;
    void *args_;//未来执行时带的参数
};

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

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

相关文章

DeepSeek崛起:中国AI新星如何撼动全球资本市场格局

引言 近期&#xff0c;中国人工智能实验室DeepSeek发布的两款开源模型——DeepSeek V3和DeepSeek R1——以其优异的性能和低廉的成本迅速爆火&#xff0c;引发了全球资本市场的震动&#xff0c;尤其对美国资本市场产生了显著影响。DeepSeek R1更是能够在数学、代码和推理任务上…

【物联网】ARM核常用指令(详解):数据传送、计算、位运算、比较、跳转、内存访问、CPSR/SPSR、流水线及伪指令

文章目录 指令格式&#xff08;重点&#xff09;1. 立即数2. 寄存器位移 一、数据传送指令1. MOV指令2. MVN指令3. LDR指令 二、数据计算指令1. ADD指令1. SUB指令1. MUL指令 三、位运算指令1. AND指令2. ORR指令3. EOR指令4. BIC指令 四、比较指令五、跳转指令1. B/BL指令2. l…

图像处理算法研究的程序框架

目录 1 程序框架简介 2 C#图像读取、显示、保存模块 3 C动态库图像算法模块 4 C#调用C动态库 5 演示Demo 5.1 开发环境 5.2 功能介绍 5.3 下载地址 参考 1 程序框架简介 一个图像处理算法研究的常用程序逻辑框架&#xff0c;如下图所示 在该框架中&#xff0c;将图像处…

病理AI领域基础模型及多实例学习方法的性能评估|顶刊精析·25-01-27

小罗碎碎念 这篇论文聚焦于组织学全切片图像分析&#xff0c;旨在探究多实例学习&#xff08;MIL&#xff09;与基础模型&#xff08;FMs&#xff09;结合的效果。 由于全切片图像&#xff08;WSI&#xff09;分析面临标注有限和模型直接处理困难等问题&#xff0c;MIL成为常用…

Tensor 基本操作2 理解 tensor.max 操作,沿着给定的 dim 是什么意思 | PyTorch 深度学习实战

前一篇文章&#xff0c;Tensor 基本操作1 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 目录 Tensor 基本操作torch.max默认指定维度 Tensor 基本操作 torch.max torch.max 实现降维运算&#xff0c;基于指定的 d…

以太网详解(六)OSI 七层模型

文章目录 OSI : Open System Interconnect&#xff08;Reference Model&#xff09;第七层&#xff1a;应用层&#xff08;Application&#xff09;第六层&#xff1a;表示层&#xff08;Presentation&#xff09;第五层&#xff1a;会话层&#xff08;Session&#xff09;第四…

Spring MVC异常处理机制

文章目录 1. 异常处理的思路2. 异常处理两种方式3. 简单异常处理器SimpleMappingExceptionResolver 1. 异常处理的思路 系统中异常包括两类&#xff1a;预期异常和运行时异常RuntimeException&#xff0c;前者通过捕获异常从而获取异常信息&#xff0c;后者主要通过规范代码开发…

本地大模型编程实战(03)语义检索(2)

文章目录 准备按批次嵌入加载csv文件&#xff0c;分割文档并嵌入测试嵌入效果总结代码 上一篇文章&#xff1a; 本地大模型编程实战(02)语义检索(1) 详细介绍了如何使用 langchain 实现语义检索&#xff0c;为了演示方便&#xff0c;使用的是 langchain 提供的内存数据库。 在实…

[Dialog屏幕开发] 设置方式对话框

阅读该篇文章之前&#xff0c;可先阅读下述资料 [Dialog屏幕开发] 设置搜索帮助https://blog.csdn.net/Hudas/article/details/145381433?spm1001.2014.3001.5501https://blog.csdn.net/Hudas/article/details/145381433?spm1001.2014.3001.5501上篇文章我们的屏幕已实现了如…

【JavaEE进阶】Spring留言板实现

目录 &#x1f38d;预期结果 &#x1f340;前端代码 &#x1f384;约定前后端交互接口 &#x1f6a9;需求分析 &#x1f6a9;接口定义 &#x1f333;实现服务器端代码 &#x1f6a9;lombok介绍 &#x1f6a9;代码实现 &#x1f334;运行测试 &#x1f384;前端代码实…

1.23学习

misc buuctf-小明的保险箱 打开附件是一个在线图片首先将其另存为&#xff0c;然后仅仅只是一个图片&#xff0c;而无其他信息&#xff0c;那么我们再进行binwalk或者foremost文件分离&#xff0c;得到了一个文件夹&#xff0c;其中含有一个压缩包但是是一个加密的&#xff0…

【Python】第五弹---深入理解函数:从基础到进阶的全面解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、函数 1.1、函数是什么 1.2、语法格式 1.3、函数参数 1.4、函数返回值 1.5、变量作用域 1.6、函数…

【数据结构】(1)集合类的认识

一、什么是数据结构 1、数据结构的定义 数据结构就是存储、组织数据的方式&#xff0c;即相互之间存在一种或多种关系的数据元素的集合。 2、学习数据结构的目的 在实际开发中&#xff0c;我们需要使用大量的数据。为了高效地管理这些数据&#xff0c;实现增删改查等操作&…

大数据Hadoop入门2

第三部分&#xff08;Hadoop MapReduce和Hadoop YARN&#xff09; 1.课程内容-大纲-学习目标 2.理解先分再合、分而治之的思想 3.hadoop团队针对MapReduce的设计构思 map这里不能翻译成地图&#xff0c;翻译为mapping比较好一点 4.Hadoop MapReduce介绍、阶级划分和进程组成 5…

什么是BFF?他有什么用?

BFF&#xff08;Backend for Frontend&#xff09; 是一种架构模式&#xff0c;专门为前端应用提供定制化的后端服务。它的核心思想是为不同的前端客户端&#xff08;如 Web、移动端、桌面端等&#xff09;提供专门的后端服务&#xff0c;而不是让所有客户端共享同一个通用的后…

【深度之眼cs231n第七期】笔记(三十一)

目录 强化学习什么是强化学习&#xff1f;马尔可夫决策过程&#xff08;MDP&#xff09;Q-learning策略梯度SOTA深度强化学习 还剩一点小尾巴&#xff0c;还是把它写完吧。&#xff08;距离我写下前面那行字又过了好几个月了【咸鱼本鱼】&#xff09;&#xff08;汗颜&#xff…

K8S极简教程(4小时快速学会)

1. K8S 概览 1.1 K8S 是什么 K8S官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ 1.2 K8S核心特性 服务发现与负载均衡&#xff1a;无需修改你的应用程序即可使用陌生的服务发现机制。存储编排&#xff1a;自动挂载所选存储系统&#xff0c;包括本地存储。Secret和…

SPDK vhost介绍

目录 1. vhost技术的背景与动机Virtio 介绍virtio-blk数据路径为例 2. vhost技术的核心原理2.1 vhost-kernel2.2 vhost-user举例 2.3 SPDK vhostvhost的优势IO请求处理数据传输控制链路调整 3. SPDK vhost的实现与配置3.1 环境准备3.2 启动SPDK vhost服务3.3 创建虚拟块设备3.4…

【C++数论】880. 索引处的解码字符串|2010

本文涉及知识点 数论&#xff1a;质数、最大公约数、菲蜀定理 LeetCode880. 索引处的解码字符串 给定一个编码字符串 s 。请你找出 解码字符串 并将其写入磁带。解码时&#xff0c;从编码字符串中 每次读取一个字符 &#xff0c;并采取以下步骤&#xff1a; 如果所读的字符是…

[创业之路-270]:《向流程设计要效率》-2-企业流程架构模式 POS架构(规划、业务运营、支撑)、OES架构(业务运营、使能、支撑)

目录 一、POS架构 二、OES架构 三、POS架构与OES架构的差异 四、各自的典型示例 POS架构典型示例 OES架构典型示例 示例分析 五、各自的典型企业 POS架构典型企业 OES架构典型企业 分析 六、各自典型的流程 POS架构的典型流程 OES架构的典型流程 企业流程架构模式…