【Linux】POSIX线程库——线程控制

目录

1.线程创建方法

例:多线程创建

2.线程终止

2.1 return nulptr;

2.2 pthread_exit(nullptr);

3. 线程等待

3.1 等待原因

3.2 等待方法

线程终止的返回值问题

4.线程取消

5. 线程分离

5.1 分离原因

5.2 分离方法

6.封装线程


用的接口是POSIX线程库(即原生线程库)

  • 使用时头文件<pthrea.h>
  • 编译器命令要带-lpthread选项

1.线程创建方法

  • 参数:
  1. pthread_t * thread:输出型参数,返回线程id(tid,不是LWP)
  2. const pthread_attr_t *attr:线程属性,一般NULL默认
  3. start_routine:线程创建后要执行的回调函数
  4. arg:传给回调函数的参数
  • 错误检查:pthread出错时不会设置errno,而是通过返回值返回。原因:因为线程缺乏访问控制,如果修改全局的errno,可能会影响到其他线程。
  • 返回值:调用pthread函数时的错误码,跟回调函数返回值没关系。

例:多线程创建

#include <pthread.h>
#include <vector>
#include <iostream>
#include <unistd.h>
using namespace std;
void *start_routine(void *args)
{
    string name = static_cast<char *>(args);
    while (true)
    {
        cout << "我是线程" << name << endl;
        sleep(1);
    }
}

int main()
{
#define NUM 10
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        char buffer[64] = {};
        snprintf(buffer, sizeof(buffer), "%s:%d", "thread", i); // 给每个线程不一样的编号
        pthread_create(&tid, NULL, start_routine, buffer);
        sleep(1);//【创建时sleep和不sleep的两种不同结果】
    }

    while (true)
    {
        cout << "批量创建线程成功,我是主执行流" << endl;
        sleep(1);
    }

    return 0;
}

结果1:创建时sleep

结果2:创建时不sleep

原因分析:给线程回调函数的参数是缓冲区地址buffer,主线程和子线程的调度顺序不能确定,所以可能是先子线程打印,也可能先主线程覆盖式写入buffer,每个子线程访问的都是覆盖后的buffer。

更正:

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};

void *start_routine(void *args)
{
    sleep(1); // 子线程先不打印,让主线程先打印线程的创建情况
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (cnt--)
    {
        cout << "我是线程" << td->namebuffer << " " << "还需执行任务次数:" << cnt << endl;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    vector<ThreadData *> threads;
#define NUM 10
    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i); // 每个线程一个new一个Data对象,一个专属buffer用来存name,不会再覆盖了
        pthread_create(&td->tid, NULL, start_routine, td);
        threads.push_back(td);
        // sleep(1);
    }

    for (auto &iter : threads)
    {
        cout << "创建线程:" << iter->namebuffer << ":" << iter->tid << "success" << endl;
    }

    while (true)
    {
        cout << "批量创建线程成功,我是主执行流" << endl;
        sleep(1);
    }

    return 0;
}

start_routine处于被重入状态,是可重入函数吗?是,没有访问临界资源,函数内部定义的局部变量不会互相影响,存储在线程各自的独立栈结构。

2.线程终止

exit不能用来终止线程,因为exit终止进程。

注意主线程如果退出(return或exit),整个进程退出。

2.1 return nulptr;

2.2 pthread_exit(nullptr);

返回值都暂时设置为nullptr,在线程等待部分讲解。

3. 线程等待

入如何拿到回调函数的返回值?线程等待。

线程如果不等待回收资源,会造成内存泄漏的问题,类似僵尸进程。


3.1 等待原因

1.获取线程的退出信息

2.线程退出后,回收线程对应的PCB等内核资源,防止内存泄漏。OS没有提供方法查看"僵尸线程"。

3.2 等待方法

  • 参数:
  1. pthread_t thread:线程tid,创建线程是的输出型参数。
  2. retval:二级指针
  • 错误:由返回值int代表错误码,错误返回错误码,成功返回0。
  • 等待方式:阻塞式等待

线程终止的返回值问题

pthread_exit退出时,参数是void*;return退出时,回调函数的返回值类型必须是void*的。void *(*start_routine) (void *)

int pthread_join(pthread_t thread, void **retval);

回调函数的返回值void*和pthread_join的二级指针void**是什么关系?

首先复习:

1.返回值是void*类型,存储void*的地址就必须用void**类型的指针变量。

2.指针和指针变量严格来说不一样

指针是一个字面值,指32位或64位地址(虚拟或物理地址),只能做右值。

指针变量是一个变量,变量里保存的是地址数据,可以做左值也可以做右值。

分析pthread_join需要二级指针来拿到返回值的原因:

函数调用的返回值存储在库当中,在库为线程创建的独立栈结构上。库想要把这个void*类型的变量传递给外部的、由用户定义的接收返回值的变量,需要【接收返回值的变量】的地址,才能直接修改外部变量。

回调函数的返回值为什么是void*,而不是直接传值返回

为了返回任意类型的对象,所以传指针返回。用户接收指针后自己强转类型就行。(类比模板。同理,回调函数的参数是void*的原因也就明白喽。)

再复习,函数返回值

1.如果传值返回,对返回值的存储位置没有要求,只需要拷贝一份给接收返回值的变量

2.如果传址返回,拷贝的是地址,只能返回【堆空间变量】的地址,不能返回【栈上变量】的地址,因为函数内的局部变量会在函数调用结束后释放,拷贝拿到的就是野指针。

例:线程回调函数返回自定义类型对象的指针

class ThreadReturn
{
public:
    bool _result;
    int _exit_code;
    std::string _reason;

    ThreadReturn(bool result, int exit_code, const string &reason)
        : _result(result), _exit_code(exit_code), _reason(reason)
    {
    }

    static ThreadReturn *ThreadReturn_success()
    {
        return new ThreadReturn(true, 0, "标准正确返回");
    }
    static ThreadReturn *ThreadReturn_success(int exit_code, const string &reason) // 自定义正确返回
    {
        return new ThreadReturn(true, exit_code, reason);
    }

    static ThreadReturn *ThreadReturn_false(int exit_code, const string &reason) // 自定义错误返回
    {
        return new ThreadReturn(false, exit_code, reason);
    }
    static ThreadReturn *ThreadReturn_false()
    {
        return new ThreadReturn(false, -1, "标准错误返回");
    }
};

    // 线程等待
    for (auto &it : threads)
    {
        void *r = nullptr;
        int ret = pthread_join(it->tid, &r);
        assert(ret == 0);
        cout << "回收:" << it->namebuffer << "线程返回值:" << ((ThreadReturn *)r)->_reason << endl;
        delete it;
    }

返回的时候,你可以return ThreadReturn::ThreadReturn_success();//这本来就是一个堆地址

不要直接 return &ThreadReturn(true, 0, "标准正确返回");//这是一个栈上的变量的地址。

通过线程等待只能拿到线程回调函数的返回值,而不包含退出信号。

因为信号发送给整个进程,子进程信号通过父进程进程等待获取,或进程调用signal捕捉信号。


4.线程取消

  • 功能:一个线程可以调用pthread_ cancel终止同一进程中的线程。
  • 方法

  • 参数:pthread_t tid,就是线程创建时传入的输出型参数。
  • 返回值:int,表示cancel函数的调用情况。
  • 被取消线程的退出码(join拿到的函数返回值)是-1。

例:取消一部分线程,查看被取消线程的返回值

void *start_routine(void *args)
{
    sleep(1); // 子线程先不打印,让主线程先打印线程的创建情况
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (1)
    {
        cout << "我是线程" << td->namebuffer << endl;
        sleep(1);
    }

    // return (void *)td->number;
    return ThreadReturn::ThreadReturn_success();
}

int main()
{
    vector<ThreadData *> threads;
#define NUM 10
    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        td->number = i + 1;
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i); // 每个线程一个new一个Data对象,一个专属buffer用来存name,不会再覆盖了
        pthread_create(&td->tid, NULL, start_routine, td);
        threads.push_back(td);
    }

    for (auto &iter : threads)
    {
        cout << "创建线程:" << iter->namebuffer << ":" << iter->tid << "success" << endl;
    }

    // 线程取消
    sleep(5); // 先让线程都调度起来才能取消
    for (size_t i = 0; i < 5; i++)
    {
        auto iter = threads[i];
        pthread_cancel(iter->tid);
        cout << "取消线程" << iter->namebuffer << "success" << endl;
    }


    for (auto &it : threads) // 只能回收到被取消的线程,然后阻塞等待
    {
        void *r = nullptr;
        int ret = pthread_join(it->tid, &r);
        assert(ret == 0);
        cout << "回收:" << it->namebuffer << "线程返回值:" << (long long)r << endl;
        delete it;
    }

    return 0;
}

被取消线程将-1这个整形存入void*,这时注意别解引用*(int *)r

原因:

  • r是64位地址,转int*精度丢失
  • 转成*(uint64_t*)r,精度不丢失了,但还是不行。r里存的是-1,解引用指针变量的时候,会将变量里存的数据理解成地址-1,去访问-1地址。我们人为理解void*里存的就是函数/线程退出码,可不能解引用去访问这块地址的空间,不然会发生段错误越界访问。

这样才是对的:(long long)r

5. 线程分离

5.1 分离原因

主线程不想阻塞式等待,不在乎线程退出状态,想线程退出就自动回收线程资源。

5.2 分离方法

  • 同一进程内其他线程对目标线程进行分离

  • 也可以是线程自己分离

int pthread_detach(pthread_self());
  • 获取调用线程的tid的方法

 

返回值:调用线程的tid。当pthread_self()函数被主线程(即main函数所在的线程)调用时,它返回的是主线程的线程PID。

线程一般都是joinable的,可以被pthread_join等待。如果线程为分离状态,就不能被等待,join会失败。

线程创建后,新线程和主线程的调度顺序不确定,如果主线程先join阻塞等待,而线程分离自己在后。主线程在不知道线程分离的情况下,开始阻塞等待,子线程就算后来分离成功,主线程也不知道,所以等子线程退出,就被主线程等待成功了。

所以建议由主线程去分离新线程。主线程往后被调度时,新线程一定处于分离态。

6.封装线程

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include <cassert>

class Thread;

class Context
{
public:
    Thread *_this;
    void *_args;

public:
    Context() : _this(nullptr), _args(nullptr) {}
    ~Context() {}
};

class Thread
{
public:
    using func_t = std::function<void *(void *)>;
    const int num = 1024;

public:
    Thread(func_t func, void *args, int number)
        : _func(func), _args(args)
    {
        char buffer[num] = {0};
        snprintf(buffer, sizeof(buffer), "thread-%d", number);
        _name = buffer;
    }

    static void *start_routine(void *args) // pthread_create没有直接调用_func,为了参数类型匹配,这个函数需要是static的,但是在函数内部又需要拿到类对象的内容,所以参数为结构体(this,args)
    {
        Context *ctx = static_cast<Context *>(args);
        void *ret = ctx->_this->run(ctx->_args);
        delete ctx;
        return ret;
    }

    void *run(void *args)
    {
        return _func(args);
    }

    void start()
    {
        Context *ctx = new Context();
        ctx->_args = _args;
        ctx->_this = this;
        // int n = pthread_create(&_tid, nullptr, _func, _args);
        _func函数指针对象,和c的函数指针不能不能互相转化
        int n = pthread_create(&_tid, nullptr, start_routine, ctx); // 调静态成员函数
        assert(n == 0);
        (void)n;
    }

    void join()
    {
        int ret = pthread_join(_tid, nullptr);
        assert(ret == 0); // assert在release下不存在
        (void)ret;
    }

    ~Thread()
    {

    }

private:
    std::string _name;
    pthread_t _tid;
    func_t _func;
    void *_args;
};

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

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

相关文章

读人工智能时代与人类未来笔记13_网络57

1. jun背控制 1.1. 威慑的目的是通过威胁发动盒站来防止盒站 1.2. jun背控制的目的是通过限制甚至废除57&#xff08;或57类别&#xff09;本身来防止盒站真 1.2.1. 与盒不扩散相配合&#xff0c;以一整套详尽的条约、技术保障措施、监管和其他控制机制为支撑&#xff0c;所…

如何生成Github Badge徽章图标

如何生成徽章Badge 什么是徽章(Badge)生成小徽章shields网站开源项目的徽章lib版本徽章代码测试覆盖度开源协议Github workflow的徽章 开源代码实践效果py-enumjs-enumerate 什么是徽章(Badge) 在开源项目的README中&#xff0c;经常会见到一些徽章(Badge)小图标&#xff0c;如…

ViLT学习

多模态里程碑式的文章&#xff0c;总结了四种多模态方法&#xff0c;根据文字和图像特征特征抽取方式不通。 文章的贡献主要是速度提高了&#xff0c;使用了数据增强&#xff0c;文本的mask 学习自b站朱老师的论文讲解

无线领夹麦克风哪个品牌好?无线麦克风品牌排行榜前十名推荐

​在当今的数字化浪潮中&#xff0c;个人声音的传播和记录变得尤为重要。无论是会议中心、教室讲台还是户外探险&#xff0c;无线领夹麦克风以其卓越的便携性和连接稳定性&#xff0c;成为了人们沟通和表达的首选工具。面对市场上琳琅满目的无线麦克风选择&#xff0c;为了帮助…

小程序多端框架目前所遇问题记录

一、wx.openLocation兼容 1、申请腾讯地图key 2、配置LBS SDK&#xff0c;选择SDK最新版本 3、调用接口&#xff0c;name和address必须输入&#xff0c;不然要报错 uni.openLocation({latitude: Number(this.info.latitude),longitude: Number(this.info.longitude),name:this…

全域外卖是谁创办的公司?

全域外卖是谁创办的公司&#xff1f;这个问题是抽象的。正确的问法应该是全域外卖是谁研发的系统。 在了解全域外卖系统前&#xff0c;我们首先要了解什么是全域外卖&#xff0c;什么是全域团购。全域指的是多平台。当然这个平台是越多越好。实际上也可以理解为聚合外卖、聚合…

Java 解决 古典问题

1 问题 编写一个Java程序&#xff0c;解决以下问题&#xff1a; 2 方法 再导入java.util包下的Scanner类&#xff0c;构建Scanner对象&#xff0c;以便输入。通过对问题的分析&#xff0c;我们可以得到&#xff0c;当位数为1时&#xff0c;其返回值为1&#xff1b;当位数为2时&…

电影推荐|基于SSM+vue的电影推荐系统的设计与实现(源码+数据库+文档)

电影推荐系统 目录 基于SSM&#xff0b;vue的电影推荐系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#…

Flutter设计模式全面解析:单例模式

谈到设计模式这个“古老”的话题&#xff0c;大家先别急着划走哈&#xff0c;虽然对它再熟悉不过&#xff0c;几乎是最初开始学习编程到现在伴随着我们整个编程生涯&#xff0c;最早 Java、C 语言实现的各种设计模式到现在还会经常有所接触&#xff0c;面试中也是必问的环节&am…

IntelliJ IDEA集成Baidu Comate,商城系统支付交易功能开发实战

文章目录 Baidu Comate介绍安装配置体验安装插件配置体验注释生成代码技术问答 实战设计表生成代码导入数据 总结 Baidu Comate介绍 在科技互联网飞速发展的今天&#xff0c;百度凭借其深厚的技术积累和创新能力&#xff0c;推出了一款名为Baidu Comate智能代码助手的产品。该…

Linxu 系统中 修改 docker 镜像存放目录 修改docker默认路径。亲测有效。

1、关闭docker 服务 systemctl stop docker 2、创建新的存放路径&#xff08;-p 父级目录不存在一起创建&#xff09; mkdir /home/service/docker -p 3、移动默认路径中的镜像文件到新目录 mv /var/lib/docker/* /home/service/docker/ 4、修改docker.service 将新的路…

【C++】继承(二)深入理解继承:派生类默认成员函数与友元、静态成员的奥秘

目录 派生类的默认成员函数①派生类的构造函数②派生类的拷贝构造函数③派生类的赋值构造④派生类的析构函数 继承与友元继承与静态成员 前言 我们在上一章讲解了: 继承三部曲&#xff0c;本篇基于上次的基础继续深入了解继承的相关知识&#xff0c;欢迎大家和我一起学习继承 派…

微信小程序报错:notifyBLECharacteristicValueChange:fail:nodescriptor的解决办法

文章目录 一、发现问题二、分析问题二、解决问题 一、发现问题 微信小程序报错&#xff1a;notifyBLECharacteristicValueChange:fail:nodescriptor 二、分析问题 这个提示有点问题&#xff0c;应该是该Characteristic的Descriptor有问题&#xff0c;而不能说nodescriptor。 …

docker-file 网络

docker挂载 1.绑定挂载&#xff08;Bind Mounts&#xff09;&#xff1a;绑定挂载是将主机上的文件或目录挂载到容器中。 docker run -v /host/path:/container/path image_name 2.卷挂载&#xff08;Volume Mounts&#xff09;&#xff1a;卷挂载将 Docker 数据卷挂载到容器中…

Java开发大厂面试第23讲:说一下 JVM 的内存布局和运行原理?

JVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09;顾名思义就是用来执行 Java 程序的“虚拟主机”&#xff0c;实际的工作是将编译的 class 代码&#xff08;字节码&#xff09;翻译成底层操作系统可以运行的机器码并且进行调用执行&#xff0c;这也是 …

使用delphi11编写一个基于xls作为数据库的照片展示程序

1、创建xls文档可以参考前一篇博客&#xff0c;并使用wps将文档保存为2003格式xls后缀。 2、在form上面放置adoconnection、adotable、datasource、spinedit、timer、checkbox、image、4个button组件。 image的设置&#xff1a; Image1.Align : alClient; Image1.Center : Tr…

三台泵恒压供水站电控系统及PLC程序设计实例

本文由艺捷自动化编写&#xff0c;其旗下产品有艺捷自动化网站和易为二维码说明书小程序&#xff08;微信&#xff09; 本文以一个具体的项目案例&#xff0c;来讲述一个恒压供水站的电控柜设计过程。包括用户需求&#xff0c;材料选型&#xff0c;图纸设计&#xff0c;柜内布…

Manjaro linux install RedisGUI (RedisInsight)亲测2024-5-25

Arch 用户仓库(Arch User Repository)(AUR) 是用户选择 基于 Arch Linux 的系统 的一个主要理由。你可以在 AUR 中访问到大量的附加软件。 (LCTT 译注&#xff1a;AUR 中的 PKGBUILD 均为用户上传且未经审核&#xff0c;使用者需要自负责任&#xff0c;在构建软件包前请注意检…

ubuntu 源码安装 cloudcompare

1.系统环境&#xff1a; ubuntu18 cmake&#xff1a;3.10.2 官方安装指导&#xff1a;https://github.com/CloudCompare/CloudCompare/blob/master/BUILD.md (注&#xff1a;查看cmake版本&#xff1a; cmake --version) 2.安装依赖 sudo apt-get update sudo apt-get insta…

【Numpy】深入解析numpy中的ravel方法

NumPy中的ravel方法&#xff1a;一维化数组的艺术 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&#xff01;&#x1f387; &#x1f393; 博主简…