当前位置: 首页 > news >正文

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 10for(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 -1cout<<"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;}//在类内创建线程,想让线程执行对应的方法,需要将方法设置成为staticstatic 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_;//未来执行时带的参数
};

相关文章:

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

了解&#xff1a; Linux底层提供创建轻量级进程/进程的接口clone&#xff0c;通过选择是否共享资源创建。 vfork和fork都调用的clone进行实现&#xff0c;vfork和父进程共享地址空间-轻量级进程。 库函数pthread_create调用的也是底层的clone。 POSIX线程库 与线程有关的函数构…...

C++——list的了解和使用

目录 引言 forward_list与list 标准库中的list 一、list的常用接口 1.list的迭代器 2.list的初始化 3.list的容量操作 4.list的访问操作 5.list的修改操作 6.list的其他操作 二、list与vector的对比 结束语 引言 本篇博客要介绍的是STL中的list。 求点赞收藏评论…...

Agent群舞,在亚马逊云科技搭建数字营销多代理(Multi-Agent)(下篇)

在本系列的上篇中&#xff0c;小李哥为大家介绍了如何在亚马逊云科技上给社交数字营销场景创建AI代理的方案&#xff0c;用于社交动态的生成和对文章进行推广曝光。在本篇中小李哥将继续本系列的介绍&#xff0c;为大家介绍如何创建主代理&#xff0c;将多个子代理挂载到主代理…...

DBeaver连接MySQL数据库

打开DBeaver&#xff0c;点击“新建数据库连接”选项。 点击“测试连接”&#xff0c;首次连接mysql会提示下载对应的JDBC驱动&#xff0c;点击下载即可。 填写服务器地址&#xff08;这里是本地测试&#xff09;、mysql的用户名&#xff08;root&#xff09;和密码&#xff…...

Leetcode40: 组合总和 II

题目描述&#xff1a; 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 代码思路&#xff…...

win32汇编环境,对话框程序中使用进度条控件

;运行效果 ;win32汇编环境,对话框程序中使用进度条控件 ;进度条控件主要涉及的是长度单位,每步步长,推进的时间。 ;比如你的长度是1000,步长是100,每秒走1次,则10秒走完全程 ;比如你的长度是1000,步长是10,每秒走1次,则100秒走完全程,但每格格子的长度与上面一样 ;以下…...

AIGC时代下的Vue组件开发深度探索

文章目录 一、AIGC时代对Vue组件开发的深远影响二、Vue组件开发基础与最佳实践三、AIGC技术在Vue组件开发中的具体应用四、结论与展望 随着人工智能技术的飞速发展&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;时代已经悄然来临。在这个时代背景下&#xff0c;软件…...

在Kubernets Cluster中部署LVM类型的StorageClass - 上

适用场景 看到B站技术部门的文章&#xff0c;是关于如何在k8s集群部署Elastic Search和Click House等IO密集型数据库应用的。 因为要充分利用NvME SSD盘的IOPS&#xff0c;所有数据库应用都直接调用本地SSD盘做为stateful application的 Persistent Volumes。为了可用动态的分…...

一次StarRocks分析的经历

起因 有人反馈说SR&#xff0c;在系统资源还有空闲的时候&#xff0c;被操作系统杀掉了。没有日志&#xff0c;怀疑是bug&#xff0c;如果要解决这个bug。据说在网上查到要升级。请我准备一下升级。 质疑 StarRocks是一款分析型数据库&#xff0c;2021年正式开源&#xff0c…...

Django网站搭建流程

使用Django搭建网站是一个系统的过程&#xff0c;涉及从环境搭建到部署上线的多个步骤。以下是详细的流程&#xff1a; 1. 环境搭建 &#xff08;1&#xff09;安装Python Django是基于Python的Web框架&#xff0c;因此需要先安装Python。建议安装Python 3.8及以上版本。 下载地…...

Vue-day2

7.Vue的生命周期 mounted函数&#xff1a;在页面加载完毕时&#xff0c;发送异步请求&#xff0c;加载数据&#xff0c;渲染页面 createApp({date(){},methods:{},mounted:function(){console.log(Vue挂载完毕&#xff0c;发送请求获取数据)} }).mount(#{app}) 8.ajax函数库…...

Day44:列表元素的修改

在 Python 中&#xff0c;列表是一种可变的数据结构&#xff0c;意味着我们可以对列表中的元素进行修改。修改列表元素的方式有很多种&#xff0c;包括通过索引修改、切片修改、使用 append() 和 extend() 添加新元素、以及删除元素等。今天&#xff0c;我们将学习如何在列表中…...

在 AMD GPU 上使用 vLLM 的 Triton 推理服务器

Triton Inference Server with vLLM on AMD GPUs — ROCm Blogs 2025年1月8日&#xff0c;作者&#xff1a;Fabricio Flores&#xff0c;Tiffany Mintz&#xff0c;Eliot Li&#xff0c;Yao Liu&#xff0c;Ted Themistokleous&#xff0c;Brian Pickrell&#xff0c;Vish Vadl…...

day7手机拍照装备

对焦对不上&#xff1a;1、光太暗&#xff1b;2、离太近&#xff1b;3、颜色太单一没有区分点 滤镜可以后期P 渐变灰滤镜&#xff1a;均衡色彩&#xff0c;暗的地方亮一些&#xff0c;亮的地方暗一些 中灰滤镜&#xff1a;减少光差 手机支架&#xff1a;最基本70cm即可 手…...

HarmonyOS:创建应用静态快捷方式

一、前言 静态快捷方式是一种在系统中创建的可以快速访问应用程序或特定功能的链接。它通常可以在长按应用图标&#xff0c;以图标和相应的文字出现在应用图标的上方&#xff0c;用户可以迅速启动对应应用程序的组件。使用快捷方式&#xff0c;可以提高效率&#xff0c;节省了查…...

[SUCTF 2018]MultiSQL1

进去题目页面如下 发现可能注入点只有登录和注册,那么我们先注册一个用户&#xff0c;发现跳转到了/user/user.php&#xff0c; 查看用户信息,发现有传参/user/user.php?id1 用?id1 and 11,和?id1 and 12,判断为数字型注入 原本以为是简单的数字型注入&#xff0c;看到大…...

kafka-部署安装

一. 简述&#xff1a; Kafka 是一个分布式流处理平台&#xff0c;常用于构建实时数据管道和流应用。 二. 安装部署&#xff1a; 1. 依赖&#xff1a; a). Java&#xff1a;Kafka 需要 Java 8 或更高版本。 b). zookeeper&#xff1a; #tar fxvz zookeeper-3.7.0.tar.gz #…...

VUE3 使用路由守卫函数实现类型服务器端中间件效果

vue3中的router组件&#xff0c;有一个函数 router.beforeEach&#xff0c;可以实现请求中间件效果 使用方法如下&#xff1a; 前提已经在Vue3 项目中引入router组件&#xff0c;在router.js文件中加入router.beforeEach //路由守卫函数&#xff0c;类似于中间件session效果…...

|Python新手小白中级教程|第二十九章:面向对象编程(Python类的拓展延伸与10道实操题目)(5)

文章目录 前言1.类变量与实例变量2.静态方法和类方法1.静态方法2.类方法 3.实操使用1. 创建一个名为Person的类&#xff0c;包含属性name和age&#xff0c;并且有一个方法introduce()用于介绍自己的名字和年龄。2. 创建一个名为Circle的类&#xff0c;包含属性radius和color&am…...

项目概述与规划 (I)

项目概述与规划 (I) JavaScript的学习已经接近尾声了&#xff0c;最后我们将通过一个项目来讲我们在JavaScript中学习到的所有都在这个项目中展现出来&#xff0c;这个项目的DEMO来自于Udemy中的课程&#xff0c;作者是Jonas Schmedtmann&#xff1b; 项目规划 项目步骤 用户…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…...