linux|多线程(一)

主要介绍了为什么要有线程 和线程的调用 和简单的对线程进行封装。

背景知识

a.重谈地址空间

我们知道物理内存的最小单元大小是4kB
物理内存是4G那么这样的单元友1M个
操作系统先描述再组织struct page[1M]
对于32位数据字长的机器,页表有2^32条也就是4G条,每一条光保存两个地址加一个标记位 这个页表就得有36G这样大
实际上比如 0010 0010 0010 0010 0010 0010 0010 0010
在这里插入图片描述
虚拟地址>> 12 得到 页框号
虚拟地址&&oxfff 得到 页内偏移量

线程的概念

线程:在进程内部运行,是cpu调度的基本单位
进程承担系统资源的基本实体
在这里插入图片描述
进程承担系统资源的基本实体 怎么理解这句话呢?可以把进程看作家庭,国家的最小单位国是千万家~~家是社会资源分配的最小单位,把线程看作个人,线程是进程的一部分

os要单独设计线程 --新建?暂停?销毁?调度?线程要不要和进程产生关联

在windows 有 专门管理线程的数据结构

struct tcb
{
// 线程id
// 线程 优先级
// 状态上下文
// 链接属性
};

在这里插入图片描述
但是linux 中没有专门的线程,Linux 是用进程模拟的线程
linux中的执行流,我们称为轻量级进程
线程:在进程内部运行,是cpu调度的基本单位
在cpu看来被调用的都是轻量级进程

澄清进程和线程

进程 是 只有一个执行流的线程~

见一见线程

在这里插入图片描述
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

pthread_t *thread: 一个指向pthread类型的指针 , pthread 这个类型用于保存的线程的标识符
const pthread_attr_t *attr: 线程属性对象的引用,可以用来设置线程的优先级、调度策略等。如果不需要设置特殊属性,可以传入 NULL 使用默认属性。
start_routine: 指向线程开始执行的函数的指针这个函数的类型应为 void ()(void*),意味着它接受一个 void * 类型的参数,并返回 void * 类型的结果。
arg: 传递给 start_routine 函数的参数,类型为 void *。这使得你可以在创建线程时向线程函数传递数据。

#include <iostream>
#include <unistd.h>void *threadStart(void * args)
{while(true){std::cout<<"new thraed running..."<<",pid:"<<getpid()<<std::endl;sleep(1);}
}int main()
{pthread_t tid;// 新线程pthread_create(&tid, nullptr, threadStart,(void *) "thread-new"); // 相当于把页表分成了两部分// 主线程while(true){sleep(1);std::cout<<"main thraed running..."<<",pid:"<<getpid()<<std::endl;}return 0;
}

有了多进程为什么还要有多线程

线程不用再创建页表等,只需要一个pcb 创建的成本低
运行期间线程,的调度成本低,因为不需要切换页表
删除线程,比删除一个进程的成本低,只需要删除一个pcb就行了

线程的调度成本为什么更低呢?

我们切换页表只需要修改对应寄存器中的值就可以了,这个不是主要原因
最主要的原因是
在这里插入图片描述
数据会被缓存在cache中,一切换进程,cache中的数据就作废了,就需要重新cache了

为什么还要有进程呢?

线程的健壮性比较低,一个线程出错整个程序都退出了

哪些东西在多线程中是共享的

因为同一地址空间,所以代码段,数据段都是共享的,文件描述符表,每种信号的处理方式,工作目录。

哪些东西是各自都有一份的呢?

线程id,错误码,调度优先级,调度的上下文,信号屏蔽字
**最重要的:**一组寄存器,保存硬件上下文数据 线程是在调度运行的
栈:线程的栈主要用于存储函数调用的局部变量、函数参数以及返回地址。每当线程调用一个新的函数时,相关的局部变量和函数参数就会被压入该线程的栈中,形成一个新的栈帧。当函数返回时,相应的栈帧就会被弹出,恢复之前的调用环境。由于每个线程有独立的栈,它们可以并发地进行函数调用,而不会相互干扰。

几个基本的问题

问题1:主线程和新线程谁先运行?

系统会将这个新线程加入到就绪队列中等待调度。调度器会根据其自身的算法(比如时间片轮转、优先级等)来决定哪个线程获得 CPU 时间片并开始执行。

问题2:我们期望谁最后退出? main thread,你如何保证呢?

我们希望主线程后退出,如果主线程先退出,主线程退出了整个进程就结束了,可能此时新线程还没有完成任务呢。
我们用pthread_join 来保证 主线程后退出

void * RunThread(void * args)
{std:: string name;name = (const char *)args;std::cout <<name << std::endl;return args;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, RunThread,(void *)"thread - 1");int n = pthread_join(tid,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}

在这里插入图片描述

问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么?

void  PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}

我们打出来发现是一个地址
在这里插入图片描述
为什么是一个地址呢?我们等下再说

问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!

class ThreadData
{
public:int x;int y;
};
void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void  PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData x1;x1.x = 10;x1.y =20;pthread_create(&tid1, nullptr, RunThread,(void *)&x1);// PrintToHEX(tid);int n = pthread_join(tid1,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}

问题6:注意刚刚我们x1的写法其实是不标准的 为什么?

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:int x;int y;
};
void * RunThread1(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;data->x = 5;data -> y = 10;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void  PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData x1;x1.x = 10;x1.y =20;pthread_create(&tid1, nullptr, RunThread,(void *)&x1);pthread_t tid2;pthread_create(&tid2, nullptr, RunThread1,(void *)&x1);int n = pthread_join(tid1,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}

比如说这样,我们原本想要打印 10 ,20 5,10 的 结果却这样。
在这里插入图片描述
因为main 栈上的变量 这两个线程都可以看到并修改,且这个变量只有一份,当第一个线程正准备打印的时候,第二个线程修改了这两个变量的值。导致第一个线程打印出来就和第二个线程一样了。 根本原因就是 变量只有一份,我们多创建一份就好了。

而且我们不推荐直接 将main栈上的变量给新线程因为 main函数栈的变量生命周期是随main函数的,可能新线程早都结束了,但是main栈上的变量就是不结束
我们用new 可以在新线程中方便管理 传过来的变量的生命周期。

问题5: 全面看待线程函数返回

它也可以传递自定义类型哦

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:int x;int y;int Excute(){return x + y; }
};
class ThreadResult
{
public:int x;int y;int ans;void print(){std::cout<<std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(ans)<<std::endl;}
};void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;//std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;ThreadResult * result = new ThreadResult();result->ans = data->Excute();result->x = data->x;result->y = data->y;return (void*)result;
}
void  PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData * x1 = new ThreadData();x1->x = 10;x1->y = 20;pthread_create(&tid1, nullptr, RunThread,(void *)x1);ThreadResult * result =nullptr;int n = pthread_join(tid1,(void**)&result);result->print();if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}

问题6: 如何创建多线程呢?

我们通过循环的方式 创建多线程哦~~

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
void * RunThread(void * args)
{std::string name = (const char *) args;std::cout<<name<<std::endl;return args;
}
int main()
{for(int i = 0; i < 10; i++){pthread_t tid;char name[1024];snprintf(name,sizeof(name),"this is %d new thread",i+1);pthread_create(&tid, nullptr , RunThread,name);}sleep(100);return 0;
}

在这里插入图片描述
我们发现结果并不符合预期。 因为name 只有一份,主线程 运行的时候不停在覆盖 name。
我们用 new ,循环 new 十次 主线程分配的时候每一个线程都得到 一个地址,那个地址指向一个独立的堆空间,线程之间就不互相影响了。
在这里插入图片描述
ok 完美解决~~!!!

问题7: 新线程如何终止?

1.我们可以在新线程中用return
2.我们可以在新线程中用pthread_exit 注意exit 是终止整个进程。
3.我们可以在新线程中使用pthrad_cancel
线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>void *RunThread(void *args)
{while (true){std::string name = (const char *)args;std::cout << name << std::endl;sleep(3);}return args;
}int main()
{std::vector<pthread_t> tids;for (int i = 0; i < 10; i++){pthread_t tid;// char name[1024];char *name = new char[1024];snprintf(name, 1024, "this is %d new thread", i + 1);pthread_create(&tid, nullptr, RunThread, name);tids.push_back(tid);}for (auto tid : tids){// pthread_detach(tid);pthread_cancel(tid);void *ret = nullptr;int n = pthread_join(tid, &ret);std::cout << (long long int)ret << " quit.." << std::endl;// std::cout<<"n="<<n<<std::endl;}return 0;
}

在这里插入图片描述

pthread_cancel 是请求取消另一个线程,而 pthread_exit 是线程自身决定退出。

在这里插入图片描述
在这里插入图片描述
某个线程调用了exit 整个进程就结束了~
exit 你走错片场了啊。要用pthread_exit

void * RunThread(void * args)
{std::string name = (const char *) args;std::cout<<name<<std::endl;pthread_exit(args);return args;
}

在这里插入图片描述

问题8: 可以不可以不join线程,让他执行完就退出呢??可以!

我们用pthread_detach 就可以了,然后线程变成unjoinable 状态,如果此时再等待就会出错 然后终止整个进程。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>void *RunThread(void *args)
{while (true){std::string name = (const char *)args;std::cout << name << std::endl;sleep(3);}return args;
}int main()
{std::vector<pthread_t> tids;for (int i = 0; i < 10; i++){pthread_t tid;// char name[1024];char *name = new char[1024];snprintf(name, 1024, "this is %d new thread", i + 1);pthread_create(&tid, nullptr, RunThread, name);tids.push_back(tid);}for (auto tid : tids){pthread_detach(tid); // 主线程分离新线程,新线程必须存在}for (auto tid : tids){// pthread_detach(tid);pthread_cancel(tid);void *ret = nullptr;int n = pthread_join(tid, &ret);std::cout << "n:"<<n<<std::endl;// std::cout<<"n="<<n<<std::endl;}return 0;
}

在这里插入图片描述

封装线程

#include <string>
#include <pthread.h>
#include <functional>
namespace ThreadModel
{   class Thread{typedef void  (*func_t)(std::string); // ?// using func_t = std::function<void()>;public:Thread(const std::string & name,func_t func):_name(name),_func(func){}~Thread(){}void Excute(){_isrunning = true;_func(_name); // 这里传了线程名_isrunning = false;}static void * Routine(void * args) // 不用static 第一个参数是this{Thread * self  = static_cast<Thread*>(args);//  self->_func();self->Excute();return nullptr;}void start(){pthread_create(&_tid,nullptr,Routine,(void *)this);}void join(){// std::cout<<_isrunning<<std::endl;//  std::cout<<true<<std::endl;if(_isrunning) // 只有在运行中的线程才需要被等待{//std::cout<<"..."<<std::endl;pthread_join(_tid,nullptr);}}// 终止线程void stop(){if(_isrunning){_isrunning = false;pthread_cancel(_tid);}}private:std::string _name;pthread_t _tid;func_t _func; bool _isrunning;  // 主要 用于线程终止时 只有启动了的线程 才能终止      };
}

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

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

相关文章

嵌入式linux相机 转换模块

convert_manager.c #include <config.h> #include <convert_manager.h> #include <string.h>static PT_VideoConvert g_ptVideoConvertHead NULL;/*********************************************************************** 函数名称&#xff1a; Register…

异常检测在机器学习中的重要性

异常检测在机器学习中的重要性 在机器学习领域&#xff0c;异常检测&#xff08;Anomaly Detection&#xff09;是一种识别数据集中异常或不寻常模式的任务。这些异常点可能代表错误、噪声、或更有趣的是&#xff0c;它们可能揭示了某些异常行为或新现象。异常检测在许多领域都…

LabVIEW Communications LTE Application Framework 读书笔记

目录 硬件要求一台设备2台USRPUSRP-2974 示例项目的组件文件夹结构DL Host.gcompeNodeB Host.gcompUE Host.gcompBuildsCommonUSRP RIOLTE 操作模式DLeNodeBUE 项目组件单机双机UDP readUDP writeMAC TXMAC RXDL TX PHYDL RX PHYUL TX PHYUL RX PHYSINR calculationRate adapta…

Vue + litegraph.js 实现蓝图功能

Vue3 litegraph.js 实现蓝图功能 litegraph.js [github](https://github.com/jagenjo/litegraph.js) [demo](https://tamats.com/projects/litegraph/editor/)vue - html <canvas id"mycanvas" width"1524" height"720" style"border…

PostgreSQL数据库从入门到精通系列之九:PostgreSQL数据库13版本和PostgreSQL数据库14版本功能特性

PostgreSQL数据库从入门到精通系列之九:PostgreSQL数据库13版本和PostgreSQL数据库14版本功能特性 一、PostgreSQL数据库版本13新功能和特性二、PostgreSQL13相比于PostgreSQL12功能改进三、PostgreSQL数据库14版本新功能和特性一、PostgreSQL数据库版本13新功能和特性 Postg…

R语言优雅的把数据基线表(表一)导出到word

基线表&#xff08;Baseline Table&#xff09;是医学研究中常用的一种数据表格&#xff0c;用于在研究开始时呈现参与者的初始特征和状态。这些特征通常包括人口统计学数据、健康状况和疾病史、临床指标、实验室检测、生活方式、社会经济等。 本人在既往文章《scitb包1.6版本发…

无人机之机型区别与应用领域

一、多旋翼无人机 特点&#xff1a;多旋翼无人机依靠产生升力以平衡飞行器的重力&#xff0c;通过改变每个旋翼的转速来控制飞行姿态&#xff0c;能够悬停和垂直起降。他们具备体积小、重量轻、噪音小、隐蔽性好的特点&#xff0c;操作灵活且易于维护。 应用&#xff1a;多旋…

Springboot Excel 导出工具 -- EasyPoi 简介

EasyPoi是一款基于 Apache POI 的高效 Java 工具库&#xff0c;专为简化 Excel 和 Word 文档的操作而设计。以下是对 EasyPoi 的详细介绍&#xff1a; 一、概述 名称&#xff1a;EasyPoi类型&#xff1a;Java 库功能&#xff1a;简化 Excel 和 Word 文档的操作&#xff0c;包…

uni-app:文字竖直排列,并且在父级view中水平竖直对齐

一、效果 二、代码 <template><view class"parent"><text class"child">这是竖直排列的文字</text></view> </template> <script>export default {data() {return {}},methods: {},}; </script> <sty…

Vue 实现文章锚点定位,顶栏遮住了锚点,使用scrollTo代替scrollIntoView设置偏移量

在Vue中实现文章锚点功能&#xff0c;可以通过监听滚动事件来更新当前锚点的状态。以下是一个简单的示例&#xff1a; <template><div><div :id"anchor- index" v-for"(section, index) in sections" :key"index">{{ sectio…

React和Vue.js的相似性和差异性是什么?

React 和 Vue.js 都是流行的前端 JavaScript 框架&#xff0c;它们有一些相似性和差异性&#xff1a; 相似性&#xff1a; 组件化&#xff1a;React 和 Vue.js 都支持组件化开发&#xff0c;允许开发者将界面拆分为独立的组件&#xff0c;提高代码的复用性和可维护性。…

学习小记-Kafka相较于其他MQ有啥优势?

Kafka 相比于 RocketMQ 有以下几个优势&#xff1a; 1. 高吞吐量和低延迟&#xff1a; Kafka 以其出色的 I/O 性能和分布式架构设计&#xff0c;能够实现极高的吞吐量&#xff0c;每秒数百万的消息处理能力&#xff0c;适合大规模数据流处理。同时&#xff0c;Kafka 设计为…

pycharm2020 相比pycarm2017更新内容

PyCharm 是 JetBrains 开发的一款流行的 Python 集成开发环境&#xff08;IDE&#xff09;。从 2017 版到 2020 版&#xff0c;PyCharm 进行了多个版本的更新&#xff0c;添加了许多新功能和改进。以下是一些主要的更新内容和改进&#xff1a; PyCharm 2018 和 2019 的主要更新…

vault安装手册

标准配置文件 ui true cluster_addr "https://127.0.0.1:8201" api_addr "https://127.0.0.1:8200" disable_mlock truestorage "raft" {path "/path/to/raft/data"node_id "raft_node_id" }listen…

Ubuntu 24.04安装Jellyfin媒体服务器图解教程

使用 Jellyfin 等开源软件创建媒体服务器肯定能帮助您管理和跨各种设备传输媒体集合。当你有一个封闭社区时&#xff0c;这尤其有用。 什么是 Jellyfin 媒体服务器&#xff1f; Jellyfin 媒体服务器&#xff0c;顾名思义&#xff0c;是一款开源软件&#xff0c;允许用户使用本…

网络抓包工具tcpdump的使用

tcpdump tcpdump命令是基于unix系统的命令行的数据报嗅探工具&#xff0c;可以抓取流动在网卡上的数据包&#xff0c;熟悉 tcpdump 的使用能够帮助你分析调试网络数据。 原理 linux抓包是通过注册一种虚拟的底层网络协议来完成对网络报文&#xff08;准确的是网络设备&#xf…

鼠标的发明和鼠标“变形记”

注&#xff1a;机翻&#xff0c;未校对。 Who Invented the Computer Mouse? 谁发明了电脑鼠标&#xff1f; It was technology visionary and inventor Douglas Engelbart (January 30, 1925 – July 2, 2013) who revolutionized the way computers worked, turning it fr…

Flink源码学习资料

Flink系列文档脑图 由于源码分析系列文档较多&#xff0c;本人绘制了Flink文档脑图。和下面的文档目录对应。各位读者可以选择自己感兴趣的模块阅读并参与讨论。 此脑图不定期更新中…… 文章目录 以下是本人Flink 源码分析系列文档目录&#xff0c;欢迎大家查阅和参与讨论。…

伪元素::before :: after的用法?

::before 和 ::after 是 CSS 伪元素&#xff0c;用于在元素内容的前面或后面插入内容。这些伪元素不会改变文档的实际内容&#xff0c;但可以用来添加装饰性元素或文本。以下是它们的用法和一些常见示例。 基本用法 ::before ::before 伪元素用于在元素的内容之前插入内容。 …

【简洁明了】调节大模型的prompt的方法【带案例】

简明调节大模型的prompt的方法【简洁明了带案例】 1. 明确任务目标2. 提供上下文3. 指定格式4. 限制输出长度5. 使用示例6. 逐步引导7. 提供反面例子8. 使用CoT思维链9. 反复试验和调整方法九解释&#xff1a;乔哈里窗检视 最后 因为网上给出的调节prompt都 过于详细&#xff…