Linux:多线程[2] 线程控制
了解:
Linux底层提供创建轻量级进程/进程的接口clone,通过选择是否共享资源创建。
vfork和fork都调用的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] 线程控制
了解: Linux底层提供创建轻量级进程/进程的接口clone,通过选择是否共享资源创建。 vfork和fork都调用的clone进行实现,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)(下篇)
在本系列的上篇中,小李哥为大家介绍了如何在亚马逊云科技上给社交数字营销场景创建AI代理的方案,用于社交动态的生成和对文章进行推广曝光。在本篇中小李哥将继续本系列的介绍,为大家介绍如何创建主代理,将多个子代理挂载到主代理…...
DBeaver连接MySQL数据库
打开DBeaver,点击“新建数据库连接”选项。 点击“测试连接”,首次连接mysql会提示下载对应的JDBC驱动,点击下载即可。 填写服务器地址(这里是本地测试)、mysql的用户名(root)和密码ÿ…...
Leetcode40: 组合总和 II
题目描述: 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意:解集不能包含重复的组合。 代码思路ÿ…...
win32汇编环境,对话框程序中使用进度条控件
;运行效果 ;win32汇编环境,对话框程序中使用进度条控件 ;进度条控件主要涉及的是长度单位,每步步长,推进的时间。 ;比如你的长度是1000,步长是100,每秒走1次,则10秒走完全程 ;比如你的长度是1000,步长是10,每秒走1次,则100秒走完全程,但每格格子的长度与上面一样 ;以下…...
AIGC时代下的Vue组件开发深度探索
文章目录 一、AIGC时代对Vue组件开发的深远影响二、Vue组件开发基础与最佳实践三、AIGC技术在Vue组件开发中的具体应用四、结论与展望 随着人工智能技术的飞速发展,AIGC(人工智能生成内容)时代已经悄然来临。在这个时代背景下,软件…...
在Kubernets Cluster中部署LVM类型的StorageClass - 上
适用场景 看到B站技术部门的文章,是关于如何在k8s集群部署Elastic Search和Click House等IO密集型数据库应用的。 因为要充分利用NvME SSD盘的IOPS,所有数据库应用都直接调用本地SSD盘做为stateful application的 Persistent Volumes。为了可用动态的分…...
一次StarRocks分析的经历
起因 有人反馈说SR,在系统资源还有空闲的时候,被操作系统杀掉了。没有日志,怀疑是bug,如果要解决这个bug。据说在网上查到要升级。请我准备一下升级。 质疑 StarRocks是一款分析型数据库,2021年正式开源,…...
Django网站搭建流程
使用Django搭建网站是一个系统的过程,涉及从环境搭建到部署上线的多个步骤。以下是详细的流程: 1. 环境搭建 (1)安装Python Django是基于Python的Web框架,因此需要先安装Python。建议安装Python 3.8及以上版本。 下载地…...
Vue-day2
7.Vue的生命周期 mounted函数:在页面加载完毕时,发送异步请求,加载数据,渲染页面 createApp({date(){},methods:{},mounted:function(){console.log(Vue挂载完毕,发送请求获取数据)} }).mount(#{app}) 8.ajax函数库…...
Day44:列表元素的修改
在 Python 中,列表是一种可变的数据结构,意味着我们可以对列表中的元素进行修改。修改列表元素的方式有很多种,包括通过索引修改、切片修改、使用 append() 和 extend() 添加新元素、以及删除元素等。今天,我们将学习如何在列表中…...
在 AMD GPU 上使用 vLLM 的 Triton 推理服务器
Triton Inference Server with vLLM on AMD GPUs — ROCm Blogs 2025年1月8日,作者:Fabricio Flores,Tiffany Mintz,Eliot Li,Yao Liu,Ted Themistokleous,Brian Pickrell,Vish Vadl…...
day7手机拍照装备
对焦对不上:1、光太暗;2、离太近;3、颜色太单一没有区分点 滤镜可以后期P 渐变灰滤镜:均衡色彩,暗的地方亮一些,亮的地方暗一些 中灰滤镜:减少光差 手机支架:最基本70cm即可 手…...
HarmonyOS:创建应用静态快捷方式
一、前言 静态快捷方式是一种在系统中创建的可以快速访问应用程序或特定功能的链接。它通常可以在长按应用图标,以图标和相应的文字出现在应用图标的上方,用户可以迅速启动对应应用程序的组件。使用快捷方式,可以提高效率,节省了查…...
[SUCTF 2018]MultiSQL1
进去题目页面如下 发现可能注入点只有登录和注册,那么我们先注册一个用户,发现跳转到了/user/user.php, 查看用户信息,发现有传参/user/user.php?id1 用?id1 and 11,和?id1 and 12,判断为数字型注入 原本以为是简单的数字型注入,看到大…...
kafka-部署安装
一. 简述: Kafka 是一个分布式流处理平台,常用于构建实时数据管道和流应用。 二. 安装部署: 1. 依赖: a). Java:Kafka 需要 Java 8 或更高版本。 b). zookeeper: #tar fxvz zookeeper-3.7.0.tar.gz #…...
VUE3 使用路由守卫函数实现类型服务器端中间件效果
vue3中的router组件,有一个函数 router.beforeEach,可以实现请求中间件效果 使用方法如下: 前提已经在Vue3 项目中引入router组件,在router.js文件中加入router.beforeEach //路由守卫函数,类似于中间件session效果…...
|Python新手小白中级教程|第二十九章:面向对象编程(Python类的拓展延伸与10道实操题目)(5)
文章目录 前言1.类变量与实例变量2.静态方法和类方法1.静态方法2.类方法 3.实操使用1. 创建一个名为Person的类,包含属性name和age,并且有一个方法introduce()用于介绍自己的名字和年龄。2. 创建一个名为Circle的类,包含属性radius和color&am…...
项目概述与规划 (I)
项目概述与规划 (I) JavaScript的学习已经接近尾声了,最后我们将通过一个项目来讲我们在JavaScript中学习到的所有都在这个项目中展现出来,这个项目的DEMO来自于Udemy中的课程,作者是Jonas Schmedtmann; 项目规划 项目步骤 用户…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...

