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

news2024/11/18 4:40:31

目录

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;
};

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

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

相关文章

(Java企业 / 公司项目)配置Linux网络-导入虚拟机

公司给了我一个IP地址 &#xff0c;提供了一个虚拟机或者自己搭建虚拟机&#xff0c;还有提供登录的账号密码 可以查看我之前的文章 VMware Workstation Pro 17虚拟机超级详细搭建&#xff08;含redis&#xff0c;nacos&#xff0c;docker, rabbitmq&#xff0c;sentinel&…

Ribbon负载均衡(自己总结的)

文章目录 Ribbon负载均衡负载均衡解决的问题不要把Ribbon负载均衡和Eureka-Server服务器集群搞混了Ribbon负载均衡代码怎么写ribbon负载均衡依赖是怎么引入的&#xff1f; Ribbon负载均衡 负载均衡解决的问题 首先Ribbon负载均衡配合Eureka注册中心一块使用。 在SpringCloud…

学习笔记——STM32F103的V3版本——3*3矩阵键盘控制数码管

一.硬件 1.数码管 2.3*3的矩阵键盘&#xff08;自己做的模块&#xff08;手残党一枚&#xff09;&#xff09; 3.总体连接 二.在Keil5中的部分软代码 test.c中&#xff1a; #include "sys.h" #include "usart.h" #include "delay.h" #include …

1099: 希尔排序算法实现

解法&#xff1a; 希尔增量选定n/2&#xff0c; #include<iostream> #include<vector> using namespace std; int main() {int n;cin >> n;vector<int> vec(n);for (int i 0; i < n; i) cin >> vec[i];int d n / 2;for (int i 0; i <…

大摩:AI PC渗透率到2028年将达65%,联想和戴尔是最大受益者

报告预计&#xff0c;当AI PC的渗透率在2025年达到20%时&#xff0c;PC出货量将加速增长&#xff1b;微软的“CopilotPC”将成为AI PC第一个“杀手级应用”。 报告正文 从苹果M4芯片、高通骁龙X的发布&#xff0c;再到微软推出重磅“CopilotPC”&#xff0c;AI PC逐渐成为市场…

AI应用案例:服务器智能分析管理系统

服务器硬件配置、性能状态、所运行的应用系统等信息分散于多个不同的信息管理系统。人为查询判断现有的服务器资源是否满足用户需求&#xff0c;且需结合资产管理系统与Maximo基础资源、性能监控、运维管理等各个系统互不关联&#xff0c;数据分散不能为运维管理提供完整一致的…

MySql--表的基本查询(CRUD)

CRUD : Create( 创建 ), Retrieve( 读取 ) &#xff0c; Update( 更新 ) &#xff0c; Delete &#xff08;删除&#xff09; 1.insert 基本插入 创建一张表&#xff1a; -- 创建一张学生表 CREATE TABLE students ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, sn INT NO…

【软件设计师】大题

一、数据流图 基础知识 数据流图&#xff08;Data Flow Diagram,DFD&#xff09;基本图形元素&#xff1a; 外部实体&#xff08;External Agent&#xff09; 表示存在于系统之外的对象&#xff0c;用来帮助用户理解系统数据的来源和去向加工&#xff08;Process&#xff09;数…

Python设计模式之适配器模式

目录 一、适配器模式 适配器模式的组成部分 适配器模式的种类 应用场景 实现步骤 二、测试例子 一、适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过将一个现有接口转换为另一个期望的接口来让不兼容的接口能够合作…

渗透测试 一个很奇怪的支付漏洞

新手实战刷课网站、好玩又有趣&#xff01; 第一步 打开网站、任意账户名密码登陆发现验证码可重复利用 这时候我们可以试试admin账号、发现如果账号正确会提示账户已存在、反之回显账户密码错误 第二步 既然验证码可以重复利用&#xff1b;而且账号名有回显 这时候我们试…

DNS服务的部署与配置(1)

一、DNS的定义 1、域名系统&#xff08;英文&#xff1a;Domain Name System&#xff0c;缩写&#xff1a;DNS&#xff09;是互联网的一项服务。 它作为将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便地访问互联网。 DNS使用UDP端口53。 当前&#xff0…

SpringBoot 集成 Nebula

工作需求&#xff0c;开始了解图数据库&#xff0c;经过工具选型&#xff0c;最终选择nebula graph&#xff0c;并集成到springboot&#xff0c;java 环境下如何对 Nebula Graph 进行操作&#xff0c;本文整理下过程。 1、首先引入 pom 依赖 <dependency><groupId&g…

安全访问python字典:避免空键错误的艺术

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、直接访问字典键的问题 三、使用get方法安全访问字典键 四、get方法的实际应…

v-model详解

目录 原理 作用 表单类组件封装 ​编辑v-model简化代码 原理 v-model本质上是一个语法糖。例如应用在输入框上&#xff0c;就是value属性和input属性的合写。 作用 提供数据的双向绑定。 数据变&#xff0c;视图跟着变:value视图变&#xff0c;数据跟着变input 注意&…

【C++】AVL树和红黑树模拟实现

AVL树和红黑树 1. 背景2. AVL树的概念3. AVL树节点的定义4. AVL树的插入5. AVL树的旋转5.1. 左单旋5.2. 右单旋5.3. 左右单旋5.4. 右左单旋5.5. 旋转总结 6. AVL树的验证7. AVL树的性能8. 红黑树的概念9. 红黑树的节点的定义10. 红黑树的插入10.1. 情况一10.2.情况二 11. 红黑树…

2024电工杯数学建模B题高质量成品论文,包括代码数据

2024电工杯数学建模B题高质量成品论文&#xff0c;包括代码数据 完整内容见文末名片 摘要 大学时期是学生们知识学习和身体成长的重要阶段&#xff0c;良好的饮食习惯对于促进生长发育和保证身体健康具有重要意义。针对当前大学生中存在的饮食结构不合理及不良饮食习惯的问题…

React useState数组新增和删除项

在React中&#xff0c;我们可以使用useState钩子来管理组件的状态&#xff0c;其中包括数组。如何在React函数组件中对数组进行增加和删除项的操作&#xff1f; 新增项时&#xff1a;我们可以对原数组进行解构&#xff0c;并把新增项一起加入到新数组&#xff1b; 删除项时&…

2024年全国大学生电工数学建模竞赛B题解析 | 数据处理 代码 论文分享

B 题&#xff1a;大学生平衡膳食食谱的优化设计及评价 1 数据预处理2 问题一2.1 问题1.12.1.1 评价体系的构建2.1.2 指标计算2.1.3 指标计算结果2.1.4 基于层次分析法的膳食营养评价模型2.1.5 评价模型的求解 2.2 问题1.22.2.1 食物与成分间拓扑关系的构建2.2.2 微调模型的建立…

IT行业已经饱和?2024年报考计算机类专业还有出路吗?

&#x1f446;点击关注 获取更多编程干货&#x1f446; “高薪”光环加持&#xff0c;IT行业一直是不少人心仪的职业选择&#xff0c;计算机专业一度成为最热门的本科专业。 然而&#xff0c;正因报考计算机专业、想要入行IT行业的人越来越多&#xff0c;“行业饱和”、“人才…

探数API统计分享-2017年-2021年中国各省人均消费支出统计

根据2017年至2021年的统计数据&#xff0c;我国各省&#xff08;市、区&#xff09;的人均消费支出情况各不相同。其中&#xff0c;上海的人均消费支出最高&#xff0c;达到了2021年的48879元&#xff0c;位居全国之首。紧随其后的是北京&#xff0c;人均消费支出为43640元。 …