[Linux#40][线程] 线程控制 | 多线程

news2024/9/22 17:22:29

内核中有没有很明确的线程概念呢?没有的。有的是轻量级进程的概念

不会给我直接提供线程的系统调用,只会给我们提供轻量级进程的系统调用,但是我们用户,需要线程的接口!

所以 Linux 开发者提供了 pthread 线程库--应用层--轻量级接口进行封装。为用户提供直接线程的接口

  • 几乎所有的 Linux 平台,都是默认自带这个库的!
  • Linux 中编写多线程代码 需要 使用第三方 pthread

为了保证函数能接受任意指针类型,C 进行了泛型设计void *,之后进行强转即可

windows 指针 4 字节 (默认 32 位),Linux 指针 8 字节(uname -r 发现x86_64)

1. 线程创建

功能:创建一个新的线程

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数:

  • thread:输出型参数,获取创建成功的线程ID(是个地址)
  • attr:设置线程的属性,attr为nullptr表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数,不需要使可以设置为 nullptr 要传指针,才能切实拿到)

返回值:成功返回0,失败返回错误码

测试 :可以发现有两个执行流

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

using namespace std;

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    string name=static_cast<const char*>(args);
    while(true)
        {
            cout<<"new thread create success, name: "<<name<<endl;
            sleep(1);
        }
}

int main()
{

    #define NUM 10
    for(int i=0;i<NUM;++i)
        {
            pthread_t id;
            //pthread_create(&id,nullptr,start_rountine,(void*)"thread new"); 
            char namebuffer[64];
            snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
            pthread_create(&id,nullptr,start_rountine,namebuffer); 
        }


    while(true)
        {
            cout<<"new thread create success, name: main thread"<<endl;
            sleep(1);
        }

    return 0;
}

多线程发生了一些错乱,但还是可以发现是同时运行的

⭕注意:格式化传参的设置

char namebuffer[64];
snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);
pthread_create(&id,nullptr,start_rountine,namebuffer); 

第一行: char namebuffer[64];

这一行声明了一个字符数组 namebuffer,它可以存储最多 64 个字符(包括字符串结束符 \0)。

第二行: snprintf(namebuffer,sizeof(namebuffer),"%s:%d","thread",i);

这里使用了 snprintf 函数来格式化字符串并安全地写入到 namebuffer 中。snprintf 函数通常用于格式化输出,但会检查目标缓冲区的大小以防止溢出。参数解释如下:

  • namebuffer: 目标缓冲区。
  • sizeof(namebuffer): 缓冲区的大小(字节数),这里为 64。
  • "%s:%d": 格式字符串,表示一个字符串后跟一个冒号和一个整数。
  • "thread": 要插入的第一个参数,是一个字符串。
  • i: 要插入的第二个参数,是一个整数变量。

因此,这条语句将把 "thread:i" 的格式化字符串写入 namebuffer,其中 i 是某个整数值。

第三行: pthread_create(&id,nullptr,start_routine,namebuffer);

  • namebuffer: 这是传递给线程启动例程 start_routine 的参数,即包含 "thread:i" 的字符串。

线程的第三方库,不是系统调用,g++后需要-lpthread链接库

ps -aL 查看轻量级进程, LWP--light weight process

PID==LWP ,表面这个线程是主线程

  • 任何一个线程被干掉了,进程都会被干掉
  • 一个函数可以被多个执行流同时执行。叫做函数被重入
  • 全局变量是所有线程共享的,都可以访问操作
  • 调度器决定运行顺序,我们并不只知道,但肯定是主线程最后退出

循环监视窗口的打开:while :; do ps -aL | grep mythread;sleep 1;done

2. 线程等待

  1. 类似于存在僵尸,需要等待退出
  2. 获取子进程退出结果

  • 参数:
    • thread:线程ID
    • retval:利用其带回线程返回值,需深刻理解
  • 返回值:
    • 线程等待成功返回0,失败返回错误码
void *threadRoutine(void* args)
{
    return (void *)233; // 返回给主线程
}

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

    void *ret = nullptr;
    pthread_join(tid, &ret); // 默认会阻塞等待新线程退出

    cout << "new thread retval:" << (long long)(ret) << endl;
    return 0;
}

  • main thread 等待的时候,默认是阻塞等待的!
  • 主线程需要 join --对创建线程进行回收

⭕void** retval :指向指针的指针,为了调用了接口,获取线程退出的退出码

对指针解引用,代表指针所指向的目标

将拿到的返回值空间存储到自己的地址空间中,所以就是一个二级指针了,保证了实参的传递,取地址解引用得到函数返回值,存取到用户指针中。例如:解两次引用,就可以获取函数原值了

  • 直接调用 exit,会全部都直接退出
  • exit 是用来终止进程的!不能用来直接终止进程!

3. 线程终止

仅代表线程终止

  • return
  • pthread_exit

放在调用函数结尾:

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=10;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
        pthread_exit(nullptr);
    }
    return nullptr;
}

线程取消

  • pthread_cancel -1

库设置的返回值,可以查看到 一个线程如果是被取消的,退出码是-1。

void* start_rountine(void* args)
{
    //安全的进行强制类型转化
    ThreadDate* td=static_cast<ThreadDate*>(args);
    int cnt=5;
    while(cnt)
    {
        cout<<"cnt: "<<cnt<<" &cnt"<<&cnt<<endl;
        cnt--;
        sleep(1);
    }

    //正常跑完返回的100,那被取消的线程返回的是什么呢?
    return (void*)100;
}


int main()  
{
    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,start_rountine,td); 
        //这样不仅每个线程数据除了自己拿到了,主线程也全部拿到了
        threads.push_back(td);
    }


    for(auto& iter:threads)
    {
        cout<<"create thread: "<<iter->namebuffer<<" : "<<iter->tid<<" success"<<endl;
    }

    //线程取消
    sleep(5);//先让线程跑起来
    for(int i=0;i<threads.size()/2;++i)
    {
        pthread_cancel(threads[i]->tid);//创建多线程组中的某一 进行取消
        cout<<"pthread_cancel: "<<threads[i]->namebuffer<<" success"<<endl;
    }

    for(auto& iter:threads)
    {
        void* ret=nullptr;//注意是void*
        int n=pthread_join(iter->tid,&ret);//&地址是void**  
        assert(n == 0);
        (void)n;
        cout<<"join : "<<iter->namebuffer<<" success , number: "<<(long long)ret<<endl;
        delete iter;
    }
    //这里就可以看出主线程式阻塞式的等待,全部等待成功,才打印这句话
    cout<<"main thread quit!!"<<endl;

    return 0;
} 

pthread_cancel(threads[i]->tid);//创建多线程组中的某一 进行取消

4.线程实现通信 | C++中

线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递类的对象!!

举例:实现通信

结构体+初始化=>类

class Request
{
public:
    Request(int start, int end, const string &threadname)
    : start_(start), end_(end), threadname_(threadname)
    {}
public:
    int start_;
    int end_;
    string threadname_;
};

class Response
{
public:
    Response(int result, int exitcode):result_(result),exitcode_(exitcode)
    {}
public:
    int result_;   // 计算结果
    int exitcode_; // 计算结果是否可靠
};

进行测试:

将 rq 的内容传给线程函数,对 rq 获取内容执行 rsp 运算,释放 rq,返回 rsp 打印

void *sumCount(void *args) // 线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!!
{
    Request *rq = static_cast<Request*>(args); //  Request *rq = (Request*)args
    //强制转化
    Response *rsp = new Response(0,0);
    for(int i = rq->start_; i <= rq->end_; i++)
    {
        cout << rq->threadname_ << " is runing, caling..., " << i << endl;
        rsp->result_ += i;
        usleep(100000);
    }
    delete rq;//释放空间
    return rsp;
}

int main()
{
    pthread_t tid;
    Request *rq = new Request(1, 100, "thread 1");//参数指针
    pthread_create(&tid, nullptr, sumCount, rq);


    void *ret;
    pthread_join(tid, &ret);//接收指针的地址,实现传递
    Response *rsp = static_cast<Response *>(ret);
    cout << "rsp->result: " << rsp->result_ << ", exitcode: " << rsp->exitcode_ << endl;
    delete rsp;
    return 0;
}

要用地址符号接受,接收到的也是回复类的地址

pthread_join(tid, &ret);//接收指针的地址,实现传递

Response *rsp = static_cast<Response *>(ret);

也可以反映出堆空间也是被线程共享的

  • 目前,我们的原生线程,pthread库,原生线程库
  • C++11 语言本身也已经支持多线程了 vs 原生线程库

C++ 的多线程就是封装原生线程库

void threadrun()
{
    while(true)
    {
        cout << "I am a new thead for C++" << endl;
        sleep(1);
    }
}

int main()
{
    thread t1(threadrun);//调用

    t1.join();//等待

    return 0;
}

thread t1(threadrun); //调用线程函数

编译时注意!两个编译后缀都要带 g++ -o $@$^ -std=c++11 -lpthread

windows 下装的是 windows c++的库,安装的是不同的库,所以语言具有跨平台性~


5. 打印线程自己的 id

pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");
cout<<"thread id:"<<pthread_self()<<endl;


//对16进制转化的实现
std::string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);//重点研究理解
    return hex;
}

man clone专门用来创建轻量级进程,我们使用的库底层就是它

线程的概念,是库给我们来维护的,你用的原生线程库,要不要加载到内存里,加载到哪里?

  • 要--都是基于内存的
  • 线程库要维护线程概念--不用维护线程的执行流,线程库注定了要维护多个线程属性集合。线程库要不要管理这些线程呢?要,先描述再组织
  • 由用户维护,OS 之上的,所以称为用户级线程

库加载到了共享区,在堆栈之间

  • 每一个线程的库级别的 tcb 的起始地址,叫做线程的 tid
  • 除了主线程,所有其他线程的独立栈,都在共享区,具体来讲是在 pthread 库中,tid 指向的用户 tcb 中

下篇文章将讲解线程的互斥~

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

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

相关文章

成为创作者的第1024天:成长与技术积累的旅程

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 今天是我成为创作者的第1024天。回顾这段时间&#xff0c;虽然日常的忙碌充斥着生活…

roles(角色)

创建目录&#xff0c;编写剧本下载nginx: 184 mkdir /etc/ansible/playbook 185 vim /etc/ansible/playbook/nginx.yml --- - hosts: groupremote_user: roottasks:- name: 卸载httpdyum: namehttpd stateabsent- name: 安装nginxyum: …

【MySQL 09】复合查询 (带思维导图)

文章目录 &#x1f308; 一、准备工作&#x1f308; 二、多表查询⭐ 1. 多表笛卡尔积⭐ 2. 多表查询示例 &#x1f308; 三、自连接&#x1f308; 四、子查询⭐ 1. 标量子查询⭐ 2. 多行子查询 (需要插入其他博客的链接)⭐ 3. 多列子查询 (需要插入其他博客的链接)⭐ 4. 在 fro…

小米SU7销量超特斯拉,新车明年上半年发布

小米 SU7&#xff0c;一款国内新能源车品牌纯血新势力旗下首款轿车&#xff0c;上市短短 4 个月卖出超 4 万台&#xff0c;月均销量过万。 该说不说&#xff0c;这放在整个新能源汽车工业史上也足以称得上是一件小刀喇拍屁股&#xff0c;让人开了眼的事儿。 就在本月初&#x…

大模型在企业数智化转型中可以做哪些事情?

在数字化浪潮的推动下&#xff0c;企业数智化转型已成为不可逆转的趋势。作为人工智能技术的集大成者&#xff0c;大模型以其强大的数据处理能力、深度学习能力及广泛的应用场景&#xff0c;正逐步成为企业数智化转型的核心驱动力。 大模型&#xff1a;智能时代的基石 大模型…

Error: ReferenceError: ReadableStream is not defined

midway项目在build完&#xff0c;docker启动时&#xff0c;莫名地报错Error: ReferenceError: ReadableStream is not defined&#xff0c;之前一直好好地&#xff0c;初时以为是新加的代码引起&#xff0c;后来排除了。 报错如下&#xff1a; 2024-08-20 11:57:51.446 ERROR …

【教学类-76-01】20240820书包01(图案最大化)

背景需求 通义万相生成图片&#xff0c;把图案最大化的方法&#xff08;切掉白边&#xff09; 【教学类-75-01】20240817“通义万相图片最大化透明png”的修图流程-CSDN博客文章浏览阅读1.6k次&#xff0c;点赞56次&#xff0c;收藏17次。【教学类-75-01】20240817“通义万相…

Aseembly(八)-汇编语言编写程序

前言 在该系列的第六篇文章我们主要讲述了:关于栈的寄存器:SS和SP的问题 来回一下: 对于栈指针来说,栈在被开辟的时候,首先要通过SS指针去找到开辟栈的地址空间的首地址,随后,SP指针指向该栈空间的末尾的下一个空间处.当执行push指令时,sp会-2 随后将目标压入栈中 当执行pop指…

volta引发的血案

什么是volta volta用于做项目级别的node版本控制&#xff0c;当手头上的项目有多个时&#xff0c;且node版本可能还不一样&#xff0c;我们需要不断切换node版本。使用volta可以很好的解决这个问题。只需要安装volta&#xff0c;然后在下面的package.json中配置好node版本即可…

鸿蒙HarmonOS实战开发: CMake脚本编写构建NDK工程

NDK工程构建 HarmonyOS NDK默认使用CMake作为构建系统&#xff0c;随包提供了符合HarmonyOS工具链的基础配置文件ohos.toolchain.cmake&#xff0c;用于预定义CMake变量来简化开发者配置。 常用的NDK工程构建方式有&#xff1a; 从源码构建 源码构建也有不同方式&#xff1a;…

机器学习在智能复合材料中的应用与实践

在人工智能与复合材料技术融合的背景下&#xff0c;复合材料的研究和应用正迅速发展&#xff0c;创新解决方 案层出不穷。从复合材料性能的精确预测到复杂材料结构的智能设计&#xff0c;从数据驱动的材料结构优 化到多尺度分析&#xff0c;人工智能技术正以其强大的数据处理能…

深度学习设计模式之策略模式

文章目录 前言一、介绍二、特点三、详细介绍1.核心组成2.代码示例3.优缺点优点缺点 4.使用场景 总结 前言 策略模式定义一系列算法&#xff0c;封装每个算法&#xff0c;并使它们可以互换。 一、介绍 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&…

Linux:网络基础概念

网络发展 独立模式: 计算机之间相互独立; 网络互联: 多台计算机连接在一起, 完成数据共享; 局域网 LAN: 计算机数量更多了, 通过交换机和路由器连接在一起; 广域网 WAN: 将远隔千里的计算机都连在一起; 所谓 "局域网" 和 "广域网" 只是一个相对的概念. 比如…

【C++ 第十四章】红黑树

前言&#xff1a; 学习本章&#xff0c;需要先学习 AVL树的 旋转&#xff0c;因为 红黑树也需要旋转调整来平衡&#xff0c;下面讲解将不赘述 旋转的原理和操作 红黑树的旋转 和 AVL树的旋转 唯一不同的是&#xff1a;旋转的判断使用逻辑 AVL树的旋转 可以通过 平衡因子 判断…

关于c++ grpc 和 c# grpc 通信的问题 以及 grpc 认证问题

一、c 和 c# 通信 c# 端服务器 如果域名 输入的是 https &#xff0c;则 c 端需要匹配使用&#xff0c;也就是c 端需要进行安全认证。如果是http 则c 端不需要认证&#xff08;基于c#的grpc 未 通信成功&#xff09; 参考如下网址可以写一个简单的 .net grpc服务器 &#xff08…

基于java的综合小区管理系统论文.doc

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统综合小区管理系统信息管理难度大&#xff0c;容错率低&am…

diamond安装与使用

1.前言 diamond是一款用于蛋白质和翻译后DNA搜索的序列比对工具&#xff0c;专为大规模序列数据的高性能分析设计。其主要特点包括&#xff1a; - 与BLAST相比&#xff0c;蛋白质和翻译后DNA的成对比对速度快100倍至10000倍。 2. 参考 https://github.com/bbuchfink/diamond …

微知-lspci如何查看pcie设备树状结构(-t)

对于查看pcie设备列表除了看是否存在 还需要看拓扑结构。如何看&#xff1f; lspci -t以减号为分割说明 第一列数字是域段 和 bus id。比如0000:00中0000是域 00是busid 第二列 01.2中01是device id。2是functionid 如果还有下游设备device还有一个指定busid的序号

[数据集][目标检测]夜间老鼠检测数据集VOC+YOLO格式316张1类别+视频文件1个

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;316 标注数量(xml文件个数)&#xff1a;316 标注数量(txt文件个数)&#xff1a;316 标注类别…

I.MX6U交叉编译Qt项目-思维导图-学习笔记-基于正点原子阿尔法开发板

I.MX6U交叉编译Qt项目 安装交叉编译器 交叉编译器介绍 拷贝fsl-imx-x11-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-toolchain-4.1.15-2.1.0.sh至ubuntu 执行下面的指令修改脚本的权限&#xff0c;修改权限后可以看到此脚本颜色显示改变&#xff0c;说明修改成功 chmod…