Linux —— 《线程控制》
文章目录
- 前言:
- 为什么要链接pthread库?
- 线程控制:
- 线程创建:
- start_routine?
- 传递自定义类型
- 同一份栈空间?
- 线程等待:
- 返回值与参数?
- 创建多线程
- 线程终止
- 线程分离
前言:
上一文我们学习了解了线程的相关概念,我们以生动形象的概念阐释了线程这一具体的概念,我们同时也输出了一个重要的结论:“线程是CPU调度的基本单位”。在那一刻,我们同样也输出了重要的知识,在Linux中不存在进程和线程控制块这个区分,而针对于Linux,CPU只认轻量级进程,而不是什么线程控制块。
那同样,进程我们是存在控制的,例如创建、删除、等待和替换,那么针对于线程我们同样也是要对其进行管理控制的,因此本文重点讲解线程控制。
为什么要链接pthread库?
在正式讲解线程控制的具体操作,我想先来打通一个疑惑,就是为什么我们上次在写代码时,要在使用g++编译的时候加上选项-pthread
?这个操作的本质是链接pthread库,那为什么我要链接它呢?
让我们先暂时回到上文,还记得我说过,Linux本质没有线程的,就算你非说有,那也只是轻量级进程,而我们目前想认为你Linux生成的就是线程,所以就有两个概念—— “用户级线程”和“内核级线程”。
很明显这是两个不同的概念,而为了将这两个概念打通,就不得不提供接口,让用户按照普通的方式调用函数使用线程即可,也使用户也可以认为自己使用的就是现场,同时又可以兼顾Linux系统使用轻量级进程的方法。因此我们就不得不使用pthread库。
线程控制:
线程创建:
我们使用函数:pthread_create
在上一文我们也介绍和粗略的使用过,但是我们并没有研究其参数的各种含义,下面我们就来介绍和使用。
#include <pthread.h>int pthread_create(pthread_t *thread, /* 输出型参数,用以获取创建成功的线程 ID */const pthread_attr_t *attr, /* 设置创建线程的属性,使用 nullptr 表示使用默认属性 */void *(*start_routine) (void *), /* 函数地址,该线程启动后需要去执行的函数 */void *arg); /* 线程要去执行的函数的形参,没参数时可填写 nullptr */
线程创建成功时返回 0,创建失败时返回错误码。
start_routine?
这个和我们的当初创建的学习信号中自定义捕捉很像,同样是传递一个函数指针,但是这里传进来的函数的我们并没有介绍介绍
void* start_routine(void* args)
函数呈现的是这样的一种格式,这个函数和自定义捕捉的函数一致,捕捉到了信号我们就在这个函数里做动作,同理创建好线程后,这里就是线程执行的地方。举个最简单的例子:
// 创建单线程
#include <iostream>
#include <pthread.h>
#include <unistd.h>void* start_routine(void* args)
{while(true){sleep(1);std::cout << "new thread running..." << std::endl;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_routine, nullptr);while(true){std::cout << "main thread running..." << std::endl;sleep(1);}return 0;
}
传递自定义类型
要知道我们这两个参数的类型都是void*的,这就给了我们很多可能,我们不仅仅可以传递普通的类型,我们也可以传递结构体/类,这大大的提高了我们使用线程的灵活性:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>class Person
{
public:std::string Print(){return _name + ":" + std::to_string(_age);}std::string _name;int _age;
};void* start_routine(void* args)
{Person *tp = (Person *)args;std::string info = tp->Print();while (true){sleep(1);std::cout << "new thread running..." << info << std::endl;}
}int main()
{Person p;p._age = 20;p._name = "Eric";pthread_t tid;pthread_create(&tid, nullptr, start_routine, &p);while(true){std::cout << "main thread running..." << std::endl;sleep(1);}return 0;
}
同一份栈空间?
这里见识到了线程的灵活之处,不仅仅可以传递内置类型的数据,就连我们的自定义类型的数据我们都能进行传递,但是这种做法显然不是很好的。
因为在进行对象实例化的时候,对象是在main函数的栈区创建的,然后再传递给新线程。换句话说,就是主线程与新线程共用了同一个栈空间里的资源,这很明显是不对的,因为一但我对主线程的对象进行修改,新线程同样也会看到,并同样也会对其修改造成最终的数据与我们期待的不一致。这个问题通常会发生在创建多线程时,当我需要修改原来的对象然后传递给新线程2的时候,因此我们在这里不得不停下来好好思考思考。
在这里先对代码进行了修改:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>class Person
{
public:std::string Print(){return _name + ":" + std::to_string(_age);}std::string _name;int _age;
};void* start_routine(void* args)
{while(true){sleep(1);Person *tp = (Person *)args;std::string info = tp->Print();std::cout << "new thread running..." << info << std::endl;}
}int main()
{Person p;p._age = 20;p._name = "Eric";pthread_t tid;pthread_create(&tid, nullptr, start_routine, &p);std::cout << "ready to change data" << std::endl;sleep(3);// 修改数据p._name = "Alan";p._age = 18;std::cout << "data changed" << std::endl;sleep(30); // 这里我简化了,其实应该是释放新线程再退出主线程好一点。return 0;
}
这个错误不是没有解决办法的,我们要解决的就是主线程用自己的空间,新线程也用自己的空间,那很好办,直接在各自的堆上new一个就好了。这样就算你创建了两个新线程,这两个线程的数据也是在堆空间上独立的!
线程等待:
当然和之前进程一样,主线程也是要等待新线程的结束然后回收资源,这当然也是避免一种类似“僵尸进程”的情况,但是线程提供的接口我们又能做很多工作,下面我们就来看看看线程等待的具体操作。
首先认识接口函数:pthread_join
#include <pthread.h>int pthread_join(pthread_t thread, /* 被等待的线程的线程 ID */ void **retval); /* 获取被等待的线程在退出时的返回值 */
与进程不同的是,这里的线程等待默认就是阻塞等待,直到所等待的的新线程终止为止。
同样的是,等待成功会返回0,否则返回对应的错误码,而这个错误码并不与进程等待失败的那个位图一致。
测试一下:
#include <iostream>
#include <pthread.h>
#include <unistd.h>void *start_routine(void* args)
{int cnt = 5;while(cnt){std::cout << "new thread running... cnt: " << cnt << std::endl;--cnt;sleep(1);}return nullptr;
}int main()
{std::cout << "main thread running..." << std::endl;pthread_t tid;pthread_create(&tid, nullptr, start_routine, nullptr);int n = pthread_join(tid, nullptr);if(n == 0){std::cout << "wait suceess!" << std::endl;}else{std::cout << "wait failed...." << std::endl;}return 0;
}
返回值与参数?
我们在刚刚的初次使用上,并没有研究第二个参数retval,而这里要注意它的类型是void**
,而它的作用,是用来接收被等待的线程结束时的返回值,那么我们就可以浅浅的试一试使用它:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>void *start_routine(void* args)
{int cnt = 5;while(cnt){std::cout << "new thread running... cnt: " << cnt << std::endl;--cnt;sleep(1);}return (void*)"thread done"; // 返回一个stirng类型
}int main()
{std::cout << "main thread running..." << std::endl;// 线程创建pthread_t tid;pthread_create(&tid, nullptr, start_routine, nullptr);// 线程等待void *ret = nullptr; // 用来接收线程结束时的返回值int n = pthread_join(tid, &ret);std::string r = (const char *)ret; // 强制类型转换,获得返回值的合适类型if (n == 0){std::cout << "wait suceess!" << std::endl;std::cout << "new thread return: “" << r << "”"<< std::endl;}else{std::cout << "wait failed...." << std::endl;}return 0;
}
同样的我们也可以对自定义类型进行传参,比如这里我还是创建Person类,但是我这里只是new一个而不对其进行初始化,相当于直接传递了一个“空类”,然后我们在新创建的线程中,对这个“空类”进行初始化,然后我们返回这个类,看看我们是否能读取出来?
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>class Person
{
public:std::string _name;int age;
};void *start_routine(void* args)
{int cnt = 5;while(cnt){std::cout << "new thread running... cnt: " << cnt << std::endl;--cnt;sleep(1);}// 设置对象的数据,并返回Person* pt = (Person*)args;pt->_name = "Carl";pt->age = 19;return (void *)pt;
}int main()
{std::cout << "main thread running..." << std::endl;// new实例化对象Person *p = new Person;// 创建线程pthread_t tid;pthread_create(&tid, nullptr, start_routine, p);void *ret = nullptr; // 用来接收线程结束时的返回值int n = pthread_join(tid, &ret);Person *r = (Person *)ret; // 强制类型转换,获得返回值的合适类型if (n == 0){std::cout << "wait suceess!" << std::endl;std::cout << "new thread return: “" << r->_name << ":" << r->age << "”" << std::endl;}else{std::cout << "wait failed...." << std::endl;}return 0;
}
创建多线程
结合已有的知识,我们已经完全具备创建多线程,然后在主线程结束之前回收这些多线程,比如现在我想创建10个线程,然后各个线程去打印一段话就好了,最后再一次返回,主线程依次等待回收。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <vector>const pthread_t num = 10;
void *start_routine(void *args)
{std::string tt = (const char *)args;std::cout << tt << " is running..."<< std::endl;sleep(1);return args;
}int main()
{// 创建线程std::vector<pthread_t> tids;for (int i = 1; i <= num; ++i){char *name = new char[128];snprintf(name, 128, "thread_%d", i);pthread_t tid;pthread_create(&tid, nullptr, start_routine, name);tids.push_back(tid);}// 回收线程for (int i = 0; i < tids.size(); ++i){void *ret = nullptr;pthread_join(tids[i], &ret);char *r = (char *)ret;std::cout << r << "quit..." << std::endl;}return 0;
}
线程终止
如果只是想终止某个线程而不是整个进程,可以有如下 3 种方法。
- 使用 return 终止线程:非主线程可以在执行的函数中使用 return 终止当前线程。
- 使用 pthread_exit 终止线程:线程自己可以调用该函数终止自己。
- 使用 pthread_cancel 终止线程:该函数能通线程 ID 终止任意线程。
切记你最好不要用exit(1)这样的方式终止线程,因为这不仅仅会终止线程,还会直接终止掉你的进程,这个必须要额外注意!
线程分离
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放
资源,从而造成系统泄漏。如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
#include <pthread.h>int pthread_detach(pthread_t thread); // thread 是要分离出去的线程的 ID
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach( pthread_self() );
注意:这里的pthread_self()
是获取自己线程的ID,哪个线程调用这个函数就返回哪个线程的ID
pthread_self 函数获得的线程 ID 不等于内核的 LWP 值,pthread_self 函数获得的是用户级原生线程库的线程 ID,而 LWP 是内核的轻量级进程ID,它们之间是一对一的关系。
后续我们会逐一展开
**一个线程要是被分离了,那么该线程就是处于分离状态,是不能被join的!但是依旧属于进程内部,只是不再需要被等待了!**joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
- 虽然分离出去的线程已经不归主线程管了,但一般还是建议让主线程最后再退出。
- 分离出去的线程可以被 pthread_cancel 函数终止,但不能被 pthread_join 函数等待。
- 一个线程可以将其他线程分离出去,也可以将自己分离出去。
相关文章:
Linux —— 《线程控制》
文章目录 前言:为什么要链接pthread库?线程控制:线程创建:start_routine?传递自定义类型同一份栈空间? 线程等待:返回值与参数?创建多线程 线程终止线程分离 前言: 上一文我们学习…...
基于HTML+CSS的房地产销售网站设计与实现
摘 要 房地产销售系统,在二十年来互联网时代下有着巨大的意义,随着互联网不断的发展扩大,一个方便直 观的房地产管理系统的网站开发是多么地有意义,不仅打破了传统的线下看房,线下获取资讯,也给房地产从业…...
操作系统 | 学习笔记 | 王道 | 2.4死锁
2.4 死锁 文章目录 2.4 死锁2.4.1 死锁的概念2.4.2 死锁预防2.4.3 死锁避免2.4.4 死锁检测和解除 2.4.1 死锁的概念 死锁的定义 在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象&am…...
【FPGA开发】Vivado自定义封装IP核,绑定总线
支持单个文件的封装、整个工程的封装,这里用单个文件举例。 在文件工程目录下,自建一个文件夹,里面放上需要封装的verilog文件。 选择第三个,指定路径封装,找到文件所在目录 取个名,选择封装IP的路径 会…...
python的3D可视化库vedo-3 (visual模块)点对象的属性、光效、附注
文章目录 3 PointsVisual的方法3.1 对象属性3.1.1 顶点大小3.1.2 复制属性3.1.3 颜色设置3.1.4透明度设置 3.2 对象光效3.2.1 点的形状3.2.2 点的表面光效 3.3 尾随线和投影3.3.1 尾随线3.3.2 投影 3.4 给对象附加文字说明3.4.1 标注3.4.2 2D标注3.4.3 气泡说明3.4.4 旗标说明3…...
llamaindex实战-ChatEngine-ReAct Agent模式
概述 ReAct 是一种基于Agent的聊天模式,构建在数据查询引擎之上。对于每次聊天交互,代理都会进入一个 ReAct 循环: 首先决定是否使用查询引擎工具并提出适当的输入 (可选)使用查询引擎工具并观察其输出 决定是否重复…...
redis快速进门
、数据库类型认识 关系型数据库 关系型数据库是一个结构化的数据库,创建在关系模型(二维表格模型)基础上,一般面向于记录。 SQL 语句(标准数据查询语言)就是一种基于关系型数据库的语言,用于执行…...
从0开始linux(39)——线程(2)线程控制
欢迎来到博主的专栏:从0开始linux 博主ID:代码小豪 文章目录 线程创建线程标识符线程参数多线程竞争资源 回收线程detach 线程退出pthread_cancel 线程创建 线程创建的函数为pthread_create。该函数是包含在posix线程库当中,posix线程是C语言…...
International Journal of Medical Informatics投稿经历时间节点
20240423,完成投稿 20240612,按编辑要求修改后再投, with editor 20240613,under review,completed 0, accepted 0, invitation 2. 20240620, under review,completed 0, accepted 1, invitation 2. 20240626, unde…...
BUUCTF—Reverse—Java逆向解密(10)
程序员小张不小心弄丢了加密文件用的秘钥,已知还好小张曾经编写了一个秘钥验证算法,聪明的你能帮小张找到秘钥吗? 注意:得到的 flag 请包上 flag{} 提交 需要用专门的Java反编译软件:jd-gui 下载文件,发现是个class文…...
CLIP-MMA: Multi-Modal Adapter for Vision-Language Models
当前的问题 CLIP-Adapter仅单独调整图像和文本嵌入,忽略了不同模态之间的交互作用。此外,适应性参数容易过拟合训练数据,导致新任务泛化能力的损失。 动机 图1所示。多模态适配器说明。 通过一种基于注意力的 Adapter ,作者称之…...
三维扫描仪-3d扫描建模设备自动检测尺寸
在现代工业制造领域,三维扫描仪已成为实现高精度尺寸检测的关键设备。CASAIM自动化智能检测系统以其自动化三维立体扫描技术,为产品尺寸的自动检测提供了高效、可靠的解决方案。 CASAIM自动化智能检测系统通过非接触式测量方式,通过激光扫描…...
vue3+ant design vue实现日期选择器默认显示当前年,并限制用户只能选择当前年及之前~
1、思路:之前想拿当前年直接做赋值操作,实际上是行不通的,因为组件本身有数据格式限制,会出现报错,然后索性直接获取当前日期(YYYY-MM-DD)赋值给日期组件,这样不管你用的是年&#x…...
【electron-vite】搭建electron+vue3框架基础
一、拉取项目 electron-vite 中文文档地址: https://cn-evite.netlify.app/guide/ 官网网址:https://evite.netlify.app/ 版本 vue版本:vue3 构建工具:vite 框架类型:Electron JS语法:TypeScript &…...
05《存储器层次结构与接口》计算机组成与体系结构 系列课
目录 存储器层次结构概述 层次结构的定义 存储器的排名 存储器接口 处理器与存储器的速度匹配 存储器接口的定义 存储器访问命中率 两种接口 第1种方式:并行 命中率的计算 存储器访问时间 第2种方式:逐级 结语 大家好,欢迎回来。…...
elasticsearch报错fully-formed single-node cluster with cluster UUID
1.问题描述 k8s集群内部署的es中间件起不来,查看日志发现如下警告,节点发现功能开启,但是目前我是单节点服务,所以尝试编辑sts将节点发现功能去掉或者在部署时将你的sts的yaml文件和chart文件修改重新部署以去掉该功能 {"t…...
Milvus×Florence:一文读懂如何构建多任务视觉模型
近两年来多任务学习(Multi-task learning)正取代传统的单任务学习(single-task learning),逐渐成为人工智能领域的主流研究方向。其原因在于,多任务学习可以让我们以最少的人力投入,获得尽可能多…...
DAPP
02-DAPP 1 啥是 DApp? DApp,部署在链上的去中心化的应用。 DApp 是开放源代码,能运行在分布式网络上,通过网络中不同对等节点相互通信进行去中心化操作的应用。 DAPP 开放源代码,才能获得人的信任。如比特币ÿ…...
生产环境中,nginx 最多可以代理多少台服务器,这个应该考虑哪些参数 ?怎么计算呢
生产环境中,nginx 最多可以代理多少台服务器,这个应该考虑哪些参数 ?怎么计算呢 关键参数计算方法评估步骤总结 在生产环境中,Nginx最多可以代理的服务器数量并没有一个固定的限制,它取决于多个因素,包括Ng…...
【深度学习|目标跟踪】StrongSORT 详解(以及StrongSORT++)
StrongSort详解 1、论文及源码2、DeepSORT回顾3、StrongSORT的EMA4、StrongSORT的NSA Kalman5、StrongSORT的MC6、StrongSORT的BOT特征提取器7、StrongSORT的AFLink8、StrongSORT的GSI模块 1、论文及源码 论文地址:https://arxiv.org/pdf/2202.13514 源码地址&#…...
23种设计模式-原型(Prototype)设计模式
文章目录 一.什么是原型设计模式?二.原型模式的特点三.原型模式的结构四.原型模式的优缺点五.原型模式的 C 实现六.原型模式的 Java 实现七. 代码解析八.总结 类图: 原型设计模式类图 一.什么是原型设计模式? 原型模式(Prototype…...
Qt—QLineEdit 使用总结
文章参考:Qt—QLineEdit 使用总结 一、简述 QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变 QLineEdit 的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 m…...
go-zero使用自定义模板实现统一格式的 body 响应
前提 go环境的配置、goctl的安装、go-zero的基本使用默认都会 需求 go-zero框架中,默认使用goctl命令生成的代码并没有统一响应格式,现在使用自定义模板实现统一响应格式: {"code": 0,"msg": "OK","d…...
BUGKU printf
整体思路 实现循环-->获取libc版本和system函数地址->将strcpy的got表项修改为system并获得shell 第一步:实现循环 从汇编语句可以看出,在每次循环结束时若0x201700处的值是否大于1则会继续循环。 encode1会将编码后的结果保存至0x2015c0处&am…...
深度学习:梯度下降法
损失函数 L:衡量单一训练样例的效果。 成本函数 J:用于衡量 w 和 b 的效果。 如何使用梯度下降法来训练或学习训练集上的参数w和b ? 成本函数J是参数w和b的函数,它被定义为平均值; 损失函数L可以衡量你的算法效果&a…...
`console.log`调试完全指南
大家好,这里是 Geek技术前线。 今天我们来探讨 Console.log() 的一些优点。并分析一些基本概念和实践,这些可以让我们的调试工作变得更加高效。 理解前端 log 与后端 log 的区别 前端 log 与后端 log 有着显著的不同,理解这一点至关重要。…...
ROS VSCode调试方法
VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…...
16 —— Webpack多页面打包
需求:把 黑马头条登陆页面-内容页面 一起引入打包使用 步骤: 准备源码(html、css、js)放入相应位置,并改用模块化语法导出 原始content.html代码 <!DOCTYPE html> <html lang"en"><head&…...
微服务即时通讯系统的实现(服务端)----(3)
目录 1. 消息存储子服务的实现1.1 功能设计1.2 模块划分1.3 模块功能示意图1.4 数据管理1.4.1 数据库消息管理1.4.2 ES文本消息管理 1.5 接口的实现1.5.1 消息存储子服务所用到的protobuf接口实现1.5.2 最近N条消息获取接口实现1.5.3 指定时间段消息搜索接口实现1.5.4 关键字消…...
.net6.0 mvc 传递 model 实体参数(无法对 null 引用执行运行时绑定)
说一下情况: 代码没问题,能成功从数据库里查到数据,能将数据丢给ViewBag.XXXX, 在View页面也能获取到 ViewBag.XXXX的值,但是发布到线上后报这个错: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 无法对 …...
查二级建造师个人信息查询/seo是如何优化
SqlHelper详解2008-05-24 10:40SqlHelper 类实现详细信息SqlHelper 类用于通过一组静态方法来封装数据访问功能。该类不能被继承或实例化,因此将其声明为包含专用构造函数的不可继承类。在 SqlHelper 类中实现的每种方法都提供了一组一致的重载。这提供了一种很好的…...
wordpress 网店/软件培训机构排名
增删节点 数据迁移一、所有事情开始之前,先要备份好cloudera manager的数据库,以及hadoop集群中的一些组件带的数据库。这里保存了很多元数据,像hive这种丢了很麻烦的。二、如果需要换nameNode的存储目录1、备份nameNode原始数据cp -r /ddhom…...
asp在网站开发中的作用/青岛seo优化
发现空间是足够的,然后df -i 查看了下inodes,发现根目录下的inodes值使用率为63%了。 查看到底哪个目录下面的文件最多,查看前30个目录最多文件即可。 find / -xdev -printf %h\n | sort | uniq -c | sort -nr -k 1 | head -30 经查…...
wampserver做动态网站/聚合搜索引擎入口
tong()除了可以驱动蜂鸣器之外,还可以驱动步进电机(测试很好用) 一个引脚上产生一个特定频率的方波(50%占空比)。持续时间可以设定,否则波形会一直产生直到调用noTone()函数。该引脚…...
阿里云网站怎么备案域名解析/预防电信网络诈骗
在Shell脚本中要经常做各种测试,测试语句的格式:(1)test (2) [](3) [[]]三种的区别,在第三种中可以进行通配符的匹配,而且&&,||,,操作符也可以正常的存在[[]]中,但是不能存在[]中。文件测试操作…...
深圳做电商网站/推广平台排行榜
吉林实验室洗煤压滤机欢迎来电,在冬季,被过滤液也会发生变化,从而间接的影响压滤机。过滤液在这个季节,其活动性没在夏季那么强,所以其流动性也会降低一些,这样就是导致了被过滤液在穿过滤布时的速度会减慢,…...