【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 10for (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 10for (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 10for (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.mzph.cn/web/17019.shtml

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

相关文章

音视频开发—音频相关概念:数模转换、PCM数据与WAV文件详解

文章目录 前言1.模拟数字转换&#xff08;ADC&#xff09;1.1ADC的关键步骤&#xff1a; 2.数字模拟转换&#xff08;DAC&#xff09;2.1DAC 的基本流程包括&#xff1a; 3.PCM数据3.1PCM 数据的关键要素包括&#xff1a; 4.WAV文件4.1 WAV的构成4.2WAV文件的标准块结构4.3WAV的…

OpenLayers6入门,OpenLayers实现在地图上拖拽编辑修改绘制图形

专栏目录: OpenLayers6入门教程汇总目录 前言 在前面一章中,我们已经学会了如何绘制基础的三种图形线段、圆形和多边形:《OpenLayers6入门,OpenLayers图形绘制功能,OpenLayers实现在地图上绘制线段、圆形和多边形》,那么本章将在此基础上实现图形的拖拽编辑功能,方便我…

使用Java 读取PDF表格数据并保存到TXT或Excel

目录 导入相关Java库 Java读取PDF表格数据并保存到TXT Java读取PDF表格数据并保存到Excel 在日常工作中&#xff0c;我们经常需要处理来自各种来源的数据。其中&#xff0c;PDF 文件是常见的数据来源之一。这类文件通常包含丰富的信息&#xff0c;其中可能包含重要的表格数据…

FreeRtos进阶——栈保存现场的几种场景

MCU架构 在认识栈的结构前&#xff0c;我们先来认识以下单片机的简单架构。在我们的CPU中有着很重要的一个模块——寄存器&#xff08;R0-R15&#xff09;&#xff0c;其中R13&#xff0c;R14&#xff0c;R15的别称分别为SP栈顶指针、LR返回地址、PC当前指令地址。外部RAM是单片…

Android Gradle plugin 版本和Gradle 版本

1.当看到这两个版本时&#xff0c;确实有点迷糊。但是他们是独立的&#xff0c;没有太大关联。 就是说在Android studio中看到的两个版本信息&#xff0c;并无太大关联&#xff0c;是相互独立的。Gradle插件版本决定了你的项目是如何构建的&#xff0c;而Gradle版本是执行构建…

对竞品分析的理解

一、竞品分析是什么 竞品分析即对竞争对手进行分析&#xff0c;是市场研究中的一项重要工作&#xff0c;它可以帮助企业了解竞争对手的产品、策略、市场表现等信息&#xff0c;通过竞品分析可以为自己的产品制定更加精准的策略。 二、为什么要做竞品分析 1.了解市场情况 了解…

vue/core源码中ref源码的js化

起源&#xff1a; 当看见reactivity文件中的ref.ts文件长达五百多的ts代码后&#xff0c;突发奇想想看下转化成js有多少行。 进行转化&#xff1a; let shouldTrack true; // Define shouldTrack variable let activeEffect null; // Define activeEffect variable// 定义…

M2m中的采样

采样的完整代码 import torch import numpy as np from torchvision import datasets, transforms from torch.utils.data import DataLoader, WeightedRandomSampler, SubsetRandomSamplerdef get_oversampled_data(dataset, num_sample_per_class):""" Gener…

Upstream最新发布2024年汽车网络安全报告-百度网盘下载

Upstream最新发布2024年汽车网络安全报告-百度网盘下载 2024年2月7日&#xff0c;Upstream Security发布了2024年Upstream《GLOBAL AUTOMOTIVE CYBERSECURITY REPORT》。这份报告的第六版着重介绍了汽车网络安全的拐点&#xff1a;从实验性的黑客攻击发展到规模庞大的攻击&…

fpga系列 HDL 00 : 可编程逻辑器件原理

一次性可编程器件&#xff08;融保险丝实现&#xff09; 一次性可编程器件&#xff08;One-Time Programmable Device&#xff0c;简称 OTP&#xff09;是一种在制造后仅能编程一次的存储设备。OTP器件在编程后数据不可更改。这些器件在很多应用场景中具有独特的优势和用途。 …

【LeetCode算法】第83题:删除排序链表中的重复元素

目录 一、题目描述 二、初次解答 三、官方解法 四、总结 一、题目描述 二、初次解答 1. 思路&#xff1a;双指针法&#xff0c;只需遍历一遍。使用low指向前面的元素&#xff0c;high用于查找low后面与low不同内容的节点。将具有不同内容的节点链接在low后面&#xff0c;实…

【c++】菱形虚拟继承的虚函数表如何继承

请看如下代码 #include <iostream>// 基类 class Base { public:virtual void foo() { std::cout << "Base::foo()" << std::endl; }virtual void bar() { std::cout << "Base::bar()" << std::endl; } };// 虚拟继承的中间…

西门子S7-1200加入MRP 环网用法

MRP&#xff08;介质冗余&#xff09;功能概述 SIMATIC 设备采用标准的冗余机制为 MRP&#xff08;介质冗余协议&#xff09;&#xff0c;符合 IEC62439-2 标准&#xff0c;典型重新组态时间为 200ms&#xff0c;每个环网最多支持 50个设备。​博途TIA/WINCC社区VX群 ​博途T…

Linux 批量网络远程PXE

一、搭建PXE远程安装服务器 1、yum -y install tftp-server xinetd #安装tftp服务 2、修改vim /etc/xinetd.d/tftpTFTP服务的配置文件 systemctl start tftp systemctl start xinetd 3、yum -y install dhcp #---安装服务 cp /usr/share/doc/dhc…

利用Python队列生产者消费者模式构建高效爬虫

目录 一、引言 二、生产者消费者模式概述 三、Python中的队列实现 四、生产者消费者模式在爬虫中的应用 五、实例分析 生产者类&#xff08;Producer&#xff09; 消费者类&#xff08;Consumer&#xff09; 主程序 六、总结 一、引言 随着互联网的发展&#xff0c;信…

Bug:Linux用户拥有r权限但无法打开文件【Linux权限体系】

Bug&#xff1a;Linux用户拥有r权限但无法打开文件【Linux权限体系】 0 问题描述&解决 问题描述&#xff1a; 通过go编写了一个程序&#xff0c;产生的/var/log/xx日志文件发现普通用户无权限打开 - 查看文件权限发现该文件所有者、所有者组、其他用户均有r权限 - 查看该日…

5个好用的AI写论文网站推荐

目录 1.AIQuora论文写作 2.passyyds 答辩PPT 3.AIPassgo论文降AIGC 4.文状元 5.passyyds论文写作 毕业论文是每个毕业生的痛&#xff0c;不管你是本科还是硕士要想顺利毕业你就不得不面对论文。然而&#xff0c;面对论文写作时常常感到无从下手&#xff1a;有时缺乏灵感&a…

【JavaEE进阶】——要想代码不写死,必须得有spring配置(properties和yml配置文件)

目录 本章目标&#xff1a; &#x1f6a9;配置文件 &#x1f6a9;SpringBoot配置文件 &#x1f388;配置⽂件的格式 &#x1f388; properties 配置⽂件说明 &#x1f4dd;properties语法格式 &#x1f4dd;读取配置文件 &#x1f4dd;properties 缺点分析 &#x1f3…

修改uniapp内置组件checkbox的样式

默认情况下 <view style"margin-bottom: 20rpx;"><label style"display: flex;align-items: center;width: fit-content;" click"handleCheck(cxm4s)"><checkbox /><text>车信盟出险4S维保</text></label>…

3d火灾救援模拟仿真培训软件复用性强

消防VR安全逃生体验系统是深圳VR公司华锐视点引入了前沿的VR虚拟现实、web3d开发和多媒体交互技术&#xff0c;为用户打造了一个逼真的火灾现场应急逃生模拟演练环境。 相比传统的消防逃生模拟演练&#xff0c;消防VR安全逃生体验系统包含知识讲解和模拟实训演练&#xff0c;体…