Linux 线程控制

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux初窥门径⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 

目录

 前言

1.线程现象

2.线程等待

3.线程终止 

4.重新理解线程


 

 前言

在Linux 线程概念 ​​​​​​ 中 我们知道Linux中的线程是轻量级的,这就意味着Linux中是没有线程这种概念的。所以用户要想用线程,OS不会提供,Linux程序员在应用层给我们提供了第三方库 pthread库

在线程概念章节中,最后简单演示了创建线程的代码,这里就有个问题了?一个进程中什么是主线程,一个线程要被调度,cpu怎么知道该调度谁了?

1.线程现象

最后线程概念简单演示了创建线程。

简单了说了 LWP 就是轻量级进程 也是线程的标识符

 cpu通过LWP调用线程。 如果一个线程的LWP和PID的值是一样的。说明该线程就是主线程。

如果一个线程的LWP和PID的值不相等说明它是被新创建出来的线程。

那以前CPU调度进程是错的吗? 当然不是,以前我们学的是进程中单线程。所以给人感觉是CPU调度进程。说白了,我们以前学的进程单线程只是线程的子集。

 这里代码和pthread_create函数在线程概念有详细解释Linux 线程概念 

 那如果 我们给其中一个线程发送线程会怎么样?

 从上面结论我们可以得出 线程中只要有一个线程出现异常,那么其他线程都要被干掉。

讲个故事

进程就好比家庭,线程就好比家庭的成员。 家庭的成员各自干各自的事。爸妈上班,你上学,你爷爷奶奶安享晚年。大家干各自的事都是一个目的。让日子过的更好。

如果你在学校出了问题,老师是不是要请你爸妈来学校一趟,所以他们就会放下手中的工作。来学校处理你的事情。

所以一个线程出了问题,其他线程也会跟着出问题。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void show(const string &name)
{cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{while (true){cout << "new thread , pid: " << getpid() << endl;show("[新线程]");sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while (true){cout << "main thread , pid: " << getpid() << endl;show("[主线程]");sleep(1);}return 0;
}

这段代码运行结果来看, 两个线程同时都在执行函数show,说明show函数被重入了

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;void *threadRoutine(void *args)
{while (true){printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while (true){printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());g_val++;sleep(1);}return 0;
}

         

我们定义一个全局变量,可以看到两个线程都可以同时访问这个全局变量。主线程++ 新线程打印的值也是++后的值。地址也是一样。

这就说明一个问题 代码未初始区,已初始区。都是共享的。

那两个线程通信岂不是非常容易?

是的。 

2.线程等待

一个进程中,能确定那个线程先运行吗?这个还真的不能确定是主线程先运行还是新线程先运行。

但是我们能确定主线程一定是最后退出的。为什么?

因为新线程就是主线程创建出来的。主线程如果不等待新线程那么就会出现类似父子进程中的僵尸进程情况。造成资源浪费。

而原生线程库中帮我们提供了线程等待的接口函数。pthread_join 

老规矩先来了解函数原型、参数、返回值。

pthread_join - 等待一个已终止的线程

函数原型 

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

同样和pthread_create一样,需要在编译时链接-pthread选项

描述

pthread_join()函数用于等待由thread参数指定的线程终止。如果该线程已经终止,则pthread_join()会立即返回。被指定的线程必须是可加入的(joinable)。

如果retval参数不是NULL,则pthread_join()会将目标线程的退出状态(即目标线程传递给pthread_exit(3)的值)复制到*retval指向的位置。(说人话就是**retval这个参数设置为空,不关心线程的退出情况

如果目标线程被取消,则在*retval中放置PTHREAD_CANCELED

如果多个线程同时尝试加入同一个线程,则结果未定义。如果调用pthread_join()的线程被取消,则目标线程将保持可加入状态(即不会被分离)。

返回值

成功时,pthread_join()返回0;出错时,返回一个错误编号。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{int cnt = 5;while (true){// cout << "new thread , pid: " << getpid() << endl;// show("[新线程]");printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());cnt--;sleep(1);if(cnt == 0) break;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);pthread_join(tid, nullptr);cout << "main thread quit..." << endl;return 0;
}

 

从运行结果来看,前4秒钟,主线程和新线程都是同时在跑的,第5秒新线程退出,最后才是主线程退出。这里我们可以得出一个结论:主线程在等待新线程退出的这个过程中,主线程其实是阻塞等待 

这里就有一个问题,新线程退出了,执行结果主线程是怎么拿到的?

为什么要传一个二级指针? 如果是传一个一级指针。pthread_join能拿到void*这个返回值吗?

就好比 int a = 0;函数能改变外面 a的值吗?肯定不能。 如果一个函数要改变a的值,那只能传递a变量的指针, 也就是所谓 int*。 这里void*就如同a变量。,void** 就如同 int* 。

新线程返回的是void*的指针,pthread_join就要拿到 取地址void* ,通过解引用。最后就能拿到void*了。

那我们直接改造代码

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{int cnt = 5;while (true){// cout << "new thread , pid: " << getpid() << endl;// show("[新线程]");printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());cnt--;sleep(1);if(cnt == 0) break;}return (void*)1;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);// while (true)// {//     // cout << "main thread , pid: " << getpid() << endl;//     // show("[主线程]");//     printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());//     g_val++;//     sleep(1);// }void* retval;pthread_join(tid, &retval);cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;return 0;
}
代码运行结果来看我们确实拿到了1。

 

3.线程终止 

在进程中进程退出用的exit()和 _exit() 那线程也能用吗?

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{int cnt = 5;while (true){// cout << "new thread , pid: " << getpid() << endl;// show("[新线程]");printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());cnt--;sleep(1);if(cnt == 0) break;}exit(1);return (void*)1;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while (true){// cout << "main thread , pid: " << getpid() << endl;// show("[主线程]");printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());g_val++;sleep(1);}void* retval;pthread_join(tid, &retval);cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;return 0;
}

新线程退出主线程也跟着退出了,也就是说,线程只要有一个退出了,其他线程都要跟着退出,这种情况并不是我们想要的。 原生线程库也是帮我们提供线程终止的接口 pthread_exit

谁调用就会终止谁,其他线程不会受影响。

函数原型void pthread_exit(void *retval);

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;
int g_val = 0;
void show(const string &name)
{cout << name << "Say# " << "hello thread" << endl;
}
void *threadRoutine(void *args)
{int cnt = 5;while (true){// cout << "new thread , pid: " << getpid() << endl;// show("[新线程]");printf("new thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());cnt--;sleep(1);if(cnt == 0) break;}pthread_exit((void*)100);//exit(1);//return (void*)1;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);// while (true)// {//     // cout << "main thread , pid: " << getpid() << endl;//     // show("[主线程]");//     printf("main thread  g_val:%d  &_val:%p  pid:%d\n", g_val, &g_val, getpid());//     g_val++;//     sleep(1);// }void* retval;pthread_join(tid, &retval);cout << "main thread quit..." << " " << " retval: "<< (long long int)retval << endl;return 0;
}

结果如我们所预期的一样我们拿到线程回调函数的返回值。但是回调参数是void*,这意味什么?意味着我们不仅仅只能传递字符常量,函数指针,整型、等等。我们还可以传递对象啊。下面就来一个优雅的写法!!!

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>using namespace std;
class Response
{
public:Response(int exitcode, int result): _exitcode(exitcode), _result(result) {}public:int _exitcode; // 计算结果是否可靠int _result;   // 计算结果
};
class Request
{
public:Request(int start, int end, const string &threadname): _start(start), _end(end), _threadname(threadname) {}static void *SumCount(void *args){Request *rq = static_cast<Request *>(args);Response *rsp = new Response(0, 0);for (int i = rq->_start; i <= rq->_end; i++){rsp->_result += i;}return (void *)rsp;}public:int _start;int _end;string _threadname;
};int main()
{pthread_t tid;Request *rq = new Request(1, 100, "线程 1"); // 修正构造函数参数void *ret = nullptr;// 创建线程if (pthread_create(&tid, nullptr, Request::SumCount, rq) != 0){cerr << "Error creating thread" << endl;delete rq; // 如果线程创建失败,释放 rqreturn 1;}// 等待线程结束if (pthread_join(tid, &ret) != 0){cerr << "Error joining thread" << endl;}else{Response *rsp = static_cast<Response *>(ret);cout << "Result: " << rsp->_result << " " << "Exitcode: " << rsp->_exitcode << endl;delete rsp; // 释放 Response 对象}delete rq; // 释放 Request 对象return 0;
}

 

这样就符合面向对象的特性,和以前比起来就优雅太多了!!! 

获取线程id的函数接口和线程分离接口比较简单就一起介绍了

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* arg)
{pthread_detach(pthread_self());printf("%s\n", (char*)arg);return NULL;
}
int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0) {printf("create thread error\n");return 1;}int ret = 0;sleep(1);//很重要,要让线程先分离,再等待if (pthread_join(tid, NULL) == 0) {printf("pthread wait success\n");ret = 0;}else {printf("pthread wait failed\n");ret = 1;}return ret;
}

这段代码是单独说明线程分离。和前面的代码不同。C写的

string toHex(pthread_t tid)
{char hex[64];snprintf(hex, sizeof(hex), "%p", tid);return hex;
}//获取的ID是十进制的,我们转换成16进制。所以写了这个函数

打出的来的ID怎么和我们预想不一样?不应该是LWP吗? 这里就需要我们重新理解线程了。

4.重新理解线程

画了一个简单的图,这里的用户指的是程序员。由于Linux是没有线程的概念,所以说内核中OS根本就不知道线程的存在。所谓的LWP指的是轻量级进程ID,而不是线程的ID,而这个ID是OS方便管理轻量级进程,是给OS用的,而不是给用户用的。

由于我们用户是需要线程的概念,那这个所谓的线程又是谁给用户提供的?

很简单,由于我们使用的是原生线程库,这里简单解释一下什么是原生,线程库在我们安装Linux系统时,它就存在了。就如同你出生时,你的五官也是同时就存在了。所以线程库给我提供了线程的概念,而刚才打出来的tid像什么?像个地址。而且这个地址还是个高地址,那就说明了这个tid是地址空间中堆栈之间的地址,那不就是共享区吗?而线程库也是要加载内存中的。线程库又不是只有一个进程要用,其他进程也要用。这不就对上了?

那既然是线程库提供的,线程的那一套结构也是线程库维护的吗?

当然也是线程维护,为什么这么说?还是先看两个图

 这个函数看参数就很恶心,对用户使用成本太大,轻量级进程就是用的这个创建的,线程库帮我们进行了封装。变成pthread_create这些接口。

 

 pthread_create 对clone进行封装,所以每个线程,都会有个栈,而且这个栈还是独立的。而刚才讲的tid是个地址,其实是线程的起始地址,这样我们拿到了tib就能快速找到线程。

为什么线程的栈要是独立的?

线程局部存储:这是一种特殊的存储机制,允许每个线程拥有自己的变量副本,这些副本对于该线程是私有的,但对于其他线程则是不可见的。这不同于普通的局部变量,因为TLS变量的生命周期不仅限于函数调用的持续时间,而是与线程的生命周期相同。 

由于线程有了独立栈的存在,所以栈就能存储线程执行过程中的局部变量、函数调用的参数、返回地址等信息。

关于线程控制,我们学习了5个接口函数,如何创造一个线程,为什么要等待一个线程,如何终止一个线程,线程分离之后的变化,以及获取线程的tid的函数接口。 最后通过线程的tid重新认识了线程的概念。

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

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

相关文章

【APP移动端自动化测试】第四节.元素操作的API

文章目录 前言一、点击&输入&清空操作 1.1 点击元素 1.2 输入&清空元素二、获取文本内容&位置&大小操作 2.1 获取文本内容 2.2 获取位置&大小三、根据属性名获取属性值操作四、滑动和拖拽操作 4.1 _swipe 4.2 _scroll …

博瓦科技产品亮相湖北安博会啦!!!

6月12日&#xff0c;第二十三届2024中国&#xff08;武汉&#xff09;社会公共安全产品暨数字城市产业展览会&#xff08;简称&#xff1a;湖北安博会&#xff09;在武汉国际会展中心隆重开幕。作为行业内最具影响力的展会之一&#xff0c;此次盛会将汇聚来自全球的顶尖企业、专…

G6 - CycleGAN实战

&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/) 目录 理论知识CycleGAN能做什么 模型结构损失函数 模型效果总结与…

每天五分钟深度学习:逻辑回归算法完成m个样本的梯度下降

本文重点 上节课程我们学习了单样本逻辑回归算法的梯度下降,实际使用中我们肯定是m个样本的梯度下降,那么m个样本的如何完成梯度下降呢? m个样本的损失函数定义为: 我们定义第i个样本的dw、db为: dw和db为损失J对w和b的偏导数,因为m个样本的代价函数J是1到m个样本总损失…

适合各行各业在线预约的自定义小程序源码系统 前后端分离 带完整的安装代码包以及代搭建教程

系统概述 这款自定义小程序源码系统是为了适应不同行业的预约需求而设计的。它具有高度的灵活性和可扩展性&#xff0c;可以根据不同用户的需求进行定制化开发&#xff0c;满足各种复杂的业务场景。 系统的前端采用了先进的小程序技术&#xff0c;为用户提供了简洁、直观的操…

【Windows】Topaz Gigapixel AI(人工智能图片放大工具)软件介绍和安装教程

软件介绍 Topaz Gigapixel AI是一款由Topaz Labs开发的先进图像放大软件&#xff0c;利用人工智能&#xff08;AI&#xff09;技术来放大图像&#xff0c;同时保持或甚至增强图像的细节和清晰度。这款软件特别适用于需要高质量图像放大的摄影师、设计师以及其他视觉内容创作者…

【C++】模板及模板的特化

目录 一&#xff0c;模板 1&#xff0c;函数模板 什么是函数模板 函数模板原理 函数模板的实例化 推演(隐式)实例化 显示实例化 模板的参数的匹配原则 2&#xff0c;类模板 什么是类模板 类模板的实例化 二&#xff0c;模板的特化 1&#xff0c;类模板的特化 全特化…

YOLOv10在RK3588上的测试(进行中...)

1.代码源 国内镜像站在gitcode。这个镜像站也基本上包含了github上常用项目的镜像。然后它的主发布源在这里&#xff1a; GitCode - 全球开发者的开源社区,开源代码托管平台 yolov10是清华主导做的... 然后&#xff0c;在维护列表里看到了这个&#xff1a; 2024年05月31日&am…

【React】配置别名路径@

别名路径配置 1. 路径解析配置&#xff08;webpack&#xff09; CRA本身把webpack配置包装到了黑盒里无法直接修改&#xff0c;需要借助一个插件 - craco步骤 安装craco npm i -D craco/craco项目根目录下创建配置文件 craco.config.js配置文件中添加路径解析配置 const pa…

智慧检务大数据平台解决方案

1.1. 政务目标分析 1.1.1. 业务功能分析 为履行检察职能&#xff0c;人民检察院需开展职务犯罪查办和预防、刑事诉讼监督、民事行政监督、检务支持、内部管理与办公、检察队伍管理、检务保障支持等工作&#xff0c;分为 7 大类业务&#xff0c;主要功能如下&#xff1a; 1、…

白嫖Cloudflare Workers 搭建 Docker Hub镜像加速服务

简介 基于Cloudflare Workers 搭建 Docker Hub镜像加速服务。 首先要注册一个Cloudflare账号。 Cloudflare账号下域名的一级域名&#xff0c;推荐万网注册个top域名&#xff0c;再转移到Cloudflare&#xff0c;很便宜的。 注意 Worker 每天每免费账号有次数限制&#xff0c;…

PFA进口聚四氟乙烯量筒不易碎塑料量具

PFA量筒:也叫特氟龙量筒、耐腐蚀性量筒&#xff1b;低溶出与析出&#xff0c;主要用于生物医药、医药研发、新材料、痕量分析、同位素检测,ICP-MS/OES/AAS分析等实验。 常用规格:5ml、10ml、25ml、30ml、50ml、100ml、200ml、250ml、500ml、1000ml、2000ml等。 产品特性&#x…

【6】第一个Java程序:Hello World

一、引言 Java&#xff0c;作为一种广泛使用的编程语言&#xff0c;其强大的跨平台能力和丰富的库函数使其成为开发者的首选。对于初学者来说&#xff0c;编写并运行第一个Java程序是一个令人兴奋的时刻。本文将指导你使用Eclipse这一流行的集成开发环境&#xff08;IDE&#…

Vue基础面试题(二)

文章目录 1.Vue 单页应用与多页应用的区别2.Vue template 到 render 的过程3. Vue data 中某一个属性的值发生改变后&#xff0c;视图会立即同步执行重新渲染吗&#xff1f;4.Vue的优点5.vue如何监听对象或者数组某个属性的变化6.Vue模版编译原理7. 对SSR的理解8.Vue的性能优化…

实验五 网络中的树

文章目录 5.1 网络中的树第一关 认识树相关知识编程要求代码文件 第2关 根节点的二阶邻居求解方法相关知识编程要求代码文件 第3关 根节点的n阶邻居求解方法相关知识 5.2 权值矩阵与环&#xff08;无向网络&#xff09;第1关 无向网络的权值矩阵相关知识编程要求代码文件 第2关…

【机器学习】神经网络与深度学习:探索智能计算的前沿

前沿 神经网络&#xff1a;模拟人类神经系统的计算模型 基本概念 神经网络&#xff0c;又称人工神经网络&#xff08;ANN, Artificial Neural Network&#xff09;&#xff0c;是一种模拟人类神经系统结构和功能的计算模型。它由大量神经元&#xff08;节点&#xff09;相互连…

docker环境中配置phpstorm php xdebug调试工具

本文介绍通过docker compose的使用方式 第一步&#xff1a;在php镜像中安装phpxdebug扩展&#xff0c;比如php7.4对应的是xdebug3.1.6 第二步&#xff1a;设置项目中的docker-compose.yml docker-compose 增加开启xdebug的环境变量,host.docker.internal是宿主机的地址&#…

Kettle根据分类实现Excel文件拆分——kettle开发31

将整理好的一份供应商付款明细Excel文件&#xff0c;按供应商拆分成多个Excel文件。 实现思路 本文我们首先将供应商付款明细表&#xff0c;按照“名称”拆分成多份Excel文件。拆分Excel文件打算用两个转换实现&#xff0c;一个用来将Excel数据读取到参数中&#xff0c;另外一…

Internet Download Manager(IDM6.41)安装教程+软件安装包下载

IDM是一款多线程下载工具&#xff0c;全称InternetDownloadManager。IDM的多线程加速功能&#xff0c;能够充分利用宽带&#xff0c;所以下载速度会比较快&#xff0c;而且它支持断点续传。它的网站音视频捕获、站点抓取、静默下载等功能&#xff0c;也特别实用。 安 装 包 获 …

图像的几何变换之平移

文章目录 前言需求代码运行结果图 前言 图像的几何变换是一个再基础不过的知识点&#xff0c;包括等距变换&#xff0c;相似变换&#xff0c;仿射变换和投影变换。图像的几何变换是指对图像的位置&#xff0c;尺寸&#xff0c;大小&#xff0c;形状和投影进行变换&#xff0c;…