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://xiahunao.cn/news/3248214.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

随笔一、泰山派RK3566开发板调试串口波特率修改

摘要&#xff1a;立创泰山派RK3566开发板默认调试串口波特率是1500000bps&#xff0c;一般串口助手工具没有此波特率&#xff0c;为适应各种调试环境需要&#xff0c;打算修改调试串口波特率为115200bps 需要修改三个部分 1. uboot引导部分 修改tspi_linux_sdk/u-boot/config…

python数据可视化(10)——绘制地图图表

课程学习来源&#xff1a;b站up&#xff1a;【蚂蚁学python】 【课程链接&#xff1a;【【数据可视化】Python数据图表可视化入门到实战】】 【课程资料链接&#xff1a;【链接】】 python&#xff1a;3.12.3 所有库都使用最新版。 Python绘制中国地图和城市图表 from pyech…

CSS技巧专栏:一日一例 7 - 纯CSS实现炫光边框按钮特效

CSS技巧专栏&#xff1a;一日一例 7 - 纯CSS实现炫光边框按钮特效 本例效果图 案例分析 相信你可能已经在网络见过类似这样的流光的按钮&#xff0c;在羡慕别人做的按钮这么酷的时候&#xff0c;你有没有扒一下它的源代码的冲动&#xff1f;或者你当时有点冲动&#xff0c;却…

[第一期]带日期时间的LED滚动广告屏美化

效果图&#xff1a; 源代码&#xff1a; <style type"text/css">.studytextgzbox {background: #F9F9F9; border: 1px solid #999999;margin: 1px;text-align:center; float: left;line-height: 28px;height: 28px;overflow: hidden;width: 236px; }.hulik…

最新电子书|使用Anybus网关,轻松实现工业设备互联

无论何时&#xff0c;确保多网络连接 工业网关的关键角色 工业网关&#xff0c;又称为协议网关、协议转换器或协议翻译器&#xff0c;是实现工业设备互联的最简捷方法。作为信息的翻译器&#xff0c;它们使得不同工业协议的设备、机器、系统或网络能够无缝交换数据&#xff0c…

数据架构新篇章:存算一体与存算分离的协同演进

数据架构新篇章&#xff1a;存算一体与存算分离的协同演进 前言被误解的存算分离存算一体的概念存算一体的过往存算一体的演进 存算分离的定义存算分离的过往存算分离的演进 存算一体和分离示例总结 前言 降本增效大环境下&#xff0c;存算分离架构如火如荼&#xff0c;Why&am…

【STC89C51单片机】定时器中断系统

中断概念 中断是一种重要的硬件机制&#xff0c;用于在处理器正在执行程序时&#xff0c;能够及时响应某些外部或内部事件。中断可以临时中止当前正在执行的指令序列&#xff0c;转而去执行专门的中断服务程序&#xff08;ISR&#xff0c;Interrupt Service Routine&#xff0…

Stable Diffusion:解锁AI绘画新纪元的保姆级入门指南

在这个数字艺术日新月异的时代&#xff0c;Stable Diffusion如同一股清新的风&#xff0c;吹散了传统绘画的界限&#xff0c;让每个人都能成为创意无限的数字艺术家。作为一款基于Transformer结构的文本到图像生成模型&#xff0c;Stable Diffusion以其惊人的生成速度、细腻的画…

ubuntu22.04 配置grpc(优化官方教程)

优化了官方教程&#xff0c;2024.7.17顺利打通。 一&#xff1a;添加环境变量 打开root文件夹下的 .bashrc 文件 编辑文件&#xff1a;滚动到文件的底部&#xff0c;然后添加以下行&#xff1a; export MY_INSTALL_DIR$HOME/.local mkdir -p "$MY_INSTALL_DIR" exp…

AMEYA360:思瑞浦推出汽车级理想二极管ORing控制器TPS65R01Q

聚焦高性能模拟芯片和嵌入式处理器的半导体供应商思瑞浦3PEAK(股票代码&#xff1a;688536)发布汽车级理想二极管ORing控制器TPS65R01Q。 TPS65R01Q拥有20mV正向调节功能&#xff0c;降低系统损耗。快速反向关断(Typ&#xff1a;0.39μs)&#xff0c;在电池反向和各种汽车电气瞬…

注册安全分析报告:东方航空

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

base SAS programming学习笔记13(Array)

1.Array array-name{dimension} <elements> array-name&#xff1a;向量名称 dimension&#xff1a;向量长度&#xff0c;默认为1&#xff1b; elements:列出变量名&#xff0c;变量名要么全是数值变量或者全是字符变量 array-name和variable不能相同&#xff1b;也不能和…

人工智能实训室的核心功能有哪些?

随着人工智能技术的飞速发展&#xff0c;其在各行各业的应用日益广泛&#xff0c;对高素质、高技能的人工智能人才需求也随之增加。唯众紧跟市场趋势&#xff0c;致力于建设高标准、高质量的人工智能实训室&#xff0c;以满足职业院校及企业对人工智能技术应用型人才的培养需求…

HarmonyOS NEXT学习——@BuilderParam装饰器

初步理解&#xff0c;相当于VUE的插槽slot Builder function overBuilder() {}Component struct Child {label: string ChildBuilder customBuilder() {}Builder customChangeThisBuilder() {}BuilderParam customBuilderParam: () > void this.customBuilder; // 使用自定…

数据结构(双向链表)

链表的分类 链表的结构⾮常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常⽤还是两种结构&#xff1a;单链表和双向带头循环链表 1.⽆头单向⾮循环链表&#xff1a…

图论(一):速概览无向图有向图图的可视化路径问题

一、图论速概览 研究图的性质和图之间的关系节点和边组成&#xff0c;节点表示对象&#xff0c;边表示对象之间的关系无向图&#xff1a;边没有方向&#xff0c;节点之间的连接是双向的。常用于描述简单的关系&#xff0c;如社交网络中的朋友关系。根据边有无权重分为无权重无…

工业控制:CANOpen(控制器局域网络)协议快速学习

文章目录 背景协议介绍CAN总线协议CANOpen协议介绍CANOpen诞生背景CANOpen的对象字典 CANOpen的服务数据对象&#xff08;SDO&#xff09; 参考附录问题CAN总线竞争原理在CAN协议中&#xff0c;帧中的ID是发送者的ID还是接收者的ID&#xff1f; 背景 目前很多CANOpen介绍的文章…

【操作系统】文件管理——文件存储空间管理(个人笔记)

学习日期&#xff1a;2024.7.17 内容摘要&#xff1a;文件存储空间管理、文件的基本操作 在上一章中&#xff0c;我们学习了文件物理结构的管理&#xff0c;重点学习了操作系统是如何实现逻辑结构到物理结构的映射&#xff0c;这显然是针对已经存储了文件的磁盘块的&#xff0…

简单实用的企业舆情安全解决方案

前言&#xff1a;企业舆情安全重要吗&#xff1f;其实很重要&#xff0c;尤其面对负面新闻&#xff0c;主动处理和应对&#xff0c;可以掌握主动权&#xff0c;避免股价下跌等&#xff0c;那么如何做使用简单实用的企业舆情解决方案呢&#xff1f; 背景 好了&#xff0c;提取词…

【React打卡学习第一天】

React入门 一、简介二、基本使用1.引入相关js库2.babel.js的作用 二、创建虚拟DOM三、JSX&#xff08;JavaScript XML&#xff09;1.本质2.作用3.基本语法规则定义虚拟DOM时&#xff0c;不要写引号。标签中混入JS表达式时要用{}。样式的类名指定不要用class,要用className.内联…