多线程理论及操作
【一】什么是线程
-
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
-
线程顾名思义,就是一条流水线工作的过程
-
-
一条流水线必须属于一个车间,一个车间的工作过程是一个进程
-
车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线
-
流水线的工作需要电源,电源就相当于cpu
-
-
所以进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
-
多线程(即多个控制线程)的概念是在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
-
例如
-
-
北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。
-
【1】示例:
-
进程
-
-
资源单位
-
-
线程
-
-
执行单位
-
-
将操作系统比喻成大的工厂
-
-
进程相当于工厂里面的车间
-
线程相当于车间里面的流水线
-
【2】小结
-
每一个进程必定自带一个线程
-
进程:资源单位
-
-
起一个进程仅仅只是 在内存空间中开辟出一块独立的空间
-
-
线程:执行单位
-
-
真正被CPU执行的其实是进程里面的线程
-
线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要
-
-
进程和线程都是虚拟单位,只是为了我们更加方便的描述问题
【二】线程的创建开销
【1】创建进程的开销要远大于线程
-
如果我们的软件是一个工厂
-
该工厂有多条流水线
-
流水线工作需要电源
-
电源只有一个即cpu(单核cpu)
-
-
一个车间就是一个进程
-
-
-
-
一个车间至少一条流水线(一个进程至少一个线程)
-
-
-
-
创建一个进程
-
-
-
-
就是创建一个车间(申请空间,在该空间内建至少一条流水线)
-
-
-
-
而建线程
-
-
-
-
就只是在一个车间内造一条流水线
-
无需申请空间,所以创建开销小
-
-
【2】进程之间是竞争关系,线程之间是协作关系
-
车间直接是竞争/抢电源的关系,竞争
-
-
不同的进程直接是竞争关系
-
不同的程序员写的程序运行的迅雷抢占其他进程的网速
-
360把其他进程当做病毒干死
-
-
一个车间的不同流水线式协同工作的关系
-
-
同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动
-
迅雷内的线程是合作关系,不会自己干自己
-
【三】线程和进程的区别
-
Threads share the address space of the process that created it; processes have their own address space.
-
-
线程共享创建它的进程的地址空间; 进程具有自己的地址空间。
-
-
Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
-
-
线程可以直接访问其进程的数据段; 进程具有其父进程数据段的副本。
-
-
Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
-
-
线程可以直接与其进程中的其他线程通信; 进程必须使用进程间通信与同级进程进行通信。
-
-
New threads are easily created; new processes require duplication of the parent process.
-
-
新线程很容易创建; 新进程需要复制父进程。
-
-
Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
-
-
线程可以对同一进程的线程行使相当大的控制权。 进程只能控制子进程。
-
-
Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
-
-
对主线程的更改(取消,优先级更改等)可能会影响该进程其他线程的行为; 对父进程的更改不会影响子进程。
-
【四】为何要有多线程
【1】开设进程
-
申请内存空间 -- 耗资源
-
拷贝代码 - 耗资源
【2】开设线程
-
一个进程内可以开设多个线程
-
在一个进程内开设多个线程无需再次申请内存空间及拷贝代码操作
【3】总结线程的优点
-
减少了资源的消耗
-
同一个进程下的多个线程资源共享
【4】什么是多线程
-
多线程指的是
-
-
在一个进程中开启多个线程
-
简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。
-
-
多线程共享一个进程的地址空间
-
-
线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
-
-
若多个线程都是cpu密集型的,那么并不能获得性能上的增强
-
-
但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
-
-
在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于Python)
【五】开设多线程的两种方式
【1】方式一:直接调用Thread
from multiprocessing import Process
from threading import Thread
import time
def task(name):print(f'当前任务:>>>{name} 正在运行')time.sleep(3)print(f'当前任务:>>>{name} 结束运行')
def Thread_main():t = Thread(target=task, args=("dream",))# 创建线程的开销非常小,几乎代码运行的一瞬间线程就已经创建了t.start()'''当前任务:>>>dream 正在运行this is main process!this is main process!当前任务:>>>dream 结束运行'''
def Process_main():p = Process(target=task, args=("dream",))p.start()'''this is main process!当前任务:>>>dream 正在运行当前任务:>>>dream 结束运行'''
if __name__ == '__main__':Thread_main()# Process_main()print('this is main process!')
【2】方式二:继承Thread父类
from threading import Thread
import time
class MyThread(Thread):
def __init__(self, name):# 重写了别人的方法,又不知道别人的方法里面有什么, 就调用父类的方法super().__init__()self.name = name
# 定义 run 函数def run(self):print(f'{self.name} is running')time.sleep(3)print(f'{self.name} is ending')
def main():t = MyThread('dream')t.start()print(f'this is a main process')
"""dream is runningthis is a main processdream is ending"""
if __name__ == '__main__':main()
【三】一个进程下开启多个线程和多个子进程的区别
【1】线程比进程速度快
from threading import Thread
from multiprocessing import Process
import time
def work():print('hello')
def timer(func):def inner(*args, **kwargs):start_time = time.time()res = func(*args, **kwargs)print(f'函数 {func.__name__} 运行时间为:{time.time() - start_time}')return res
return inner
@timer
def work_process():# 在主进程下开启子进程t = Process(target=work)t.start()print('主线程/主进程')'''主线程/主进程函数 work_process 运行时间为:0.0043752193450927734hello'''
@timer
def work_thread():# 在主进程下开启线程t = Thread(target=work)t.start()print('主线程/主进程')'''打印结果:hello主线程/主进程函数 work_thread 运行时间为:0.0001499652862548828'''
if __name__ == '__main__':# part1 : 多线程work_thread()# part2 : 多进程work_process()
【2】查看pid
from threading import Thread
from multiprocessing import Process
import os
def work():print('hello', os.getpid())
def work_thread():# part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样t1 = Thread(target=work)t2 = Thread(target=work)t1.start()t2.start()print('主线程/主进程pid', os.getpid())
# hello 5022# hello 5022# 主线程/主进程pid 5022
def work_process():# part2:开多个进程,每个进程都有不同的pidp1 = Process(target=work)p2 = Process(target=work)p1.start()p2.start()print('主线程/主进程pid', os.getpid())
# 主线程/主进程pid 5032# hello 5034# hello 5035
if __name__ == '__main__':# part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样work_thread()# part2:开多个进程,每个进程都有不同的pidwork_process()
【3】同一进程内的线程共享进程内的数据
from threading import Thread
from multiprocessing import Process
def work():global nn = 0
def work_process():n = 100p = Process(target=work)p.start()p.join()print('主', n) # 毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
# 主 100
def work_thread():n = 1t = Thread(target=work)t.start()t.join()print('主', n) # 查看结果为1,因为同一进程内的线程之间共享进程内的数据
if __name__ == '__main__':# part1 多进程 : 子进程只改自己的work_process()# part2 多线程: 数据发生错乱,同一进程内的线程之间共享数据work_thread()
【四】守护线程
【1】主线程死亡,子线程未死亡
-
主线程结束运行后不会马上结束,而是等待其他非守护子线程结束之后才会结束
-
如果主线程死亡就代表者主进程也死亡,随之而来的是所有子线程的死亡
from threading import Thread
import time
def work(name):print(f"当前{name} 是开始\n")time.sleep(2)print(f"当前{name} 是结束")
def main():print(f'这是主函数main开始')task = Thread(target=work,args=('knight',))task.start()print(f'这是主函数main结束')
if __name__ == '__main__':main()
# 这是主函数main开始
# 当前knight 是开始
# 这是主函数main结束
# 当前knight 是结束
【2】主线程死亡,子线程也死亡
from threading import Thread
import time
def work(name):print(f"当前{name} 是开始\n")time.sleep(2)print(f"当前{name} 是结束")
def main():print(f'这是主函数main开始')task = Thread(target=work,args=('knight',))task.daemon = True # 开启守护进程,主线程结束,子线程也随之结束task.start()print(f'这是主函数main结束')
if __name__ == '__main__':main()
# 这是主函数main开始
# 当前knight 是开始
# 这是主函数main结束
示例:对比是否被守护进程的区别
# 导入所需模块 from threading import Thread import time # 定义函数foo,模拟一个耗时操作 def foo():# 打印开始信息print(f' this is foo begin')# 模拟耗时操作,暂停3秒time.sleep(3)# 打印结束信息print(f' this is foo end') # 定义另一个函数func,同样模拟耗时操作 def func():# 打印开始信息print(f' this is func begin')# 模拟耗时操作,暂停1秒time.sleep(1)# 打印结束信息print(f' this is func end') # 主函数 def main():# 创建线程 task_foo ,目标函数为footask_foo = Thread(target=foo)# 设置 task_foo 为守护线程# 意味着当主线程结束时,不论 task_foo 是否执行完毕都会被强制终止task_foo.daemon = True# 创建线程 task_func ,目标函数为functask_func = Thread(target=func) # 启动线程 task_footask_foo.start()# 启动线程 task_functask_func.start() # 主线程继续执行,打印以下信息print(f' this is main') # 程序入口 if __name__ == '__main__':main()# this is main begin # this is foo begin# this is func begin# this is main end# this is func end
执行过程
(1) 初始化阶段
-
程序开始执行时,首先会导入所需的模块,并定义两个函数
foo()和func()。 -
这两个函数分别代表了两个需要并发执行的任务。
(2)线程创建与启动
-
在
main()函数中 -
首先通过
Thread类创建了两个线程实例t1和t2 -
其中
t1的目标函数是foo,t2的目标函数是func。 -
然后将
t1设置为守护线程(daemon=True),这意味着当主线程结束时,即使t1尚未执行完毕也会被系统终止。 -
之后,两个线程通过
start()方法启动,这意味着它们将异步地执行各自的目标函数。
原理分析
(1)并发执行
-
-
t1开始执行,打印出“this is foo begin”,随后进入3秒的等待状态。 -
几乎同时,
t2也开始执行,打印出“this is func begin”,并进入1秒的等待状态。 -
由于线程调度机制,实际的打印顺序可能会略有不同,但通常情况下
func()会先于foo()结束,因为它的等待时间较短。
-
(2)主线程执行
-
主线程继续执行,打印出“this is main”。
-
由于
t1被设置为守护线程,即便它还在睡眠中,当主线程执行结束后,整个程序也会直接终止,此时t1不论是否完成都会被系统停止。 -
而
t2作为一个非守护线程,如果在主线程结束前已完成,则正常结束,否则也会随程序终止。
【五】线程的互斥锁
-
所有子线程都会进行阻塞操作,导致最后的改变只是改了一次
from threading import Thread import timemoney = 100def work():global money# 模拟获取到车票信息temp = money# 模拟网络延迟time.sleep(2)# 模拟购票money = temp - 1def main():task_list = [Thread(target=work) for i in range(100)][task.start() for task in task_list][task.join() for task in task_list]print(money)if __name__ == '__main__':main()# 99
解决方法
-
在数据发生改变的地方进行加锁处理
from threading import Thread,Lock import timemoney = 100 mutex = Lock()def work():global money# 数据发生改变之前加锁mutex.acquire()# 模拟获取到车票信息temp = money# 模拟网络延迟time.sleep(1)# 模拟购票money = temp - 1# 数据改变之后解锁mutex.release()def main():task_list = [Thread(target=work) for i in range(100)][task.start() for task in task_list][task.join() for task in task_list]print(money)if __name__ == '__main__':main()# 0
相关文章:
多线程理论及操作
【一】什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程 一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到一起ÿ…...
本周 MoonBit 核心库进行 API 整理工作、工具链持续完善
MoonBit更新 【核心库 Breaking】核心库进行API整理工作 所有immutable数据结构被放在immut路径下,如immutable_hashmap.Map变为immut/hashmap.Map // Before let a : immutable_hashmap.Map[Int, Int] immutable_hashmap.make() // After let a : immut/hashma…...
Golang net/http标准库常用方法(三)
大家好,针对Go语言 net/http 标准库,将梳理的相关知识点分享给大家~~ 围绕 net/http 标准库相关知识点还有许多章节,请大家多多关注。 文章中代码案例只有关键片段,完整代码请查看github仓库:https://github.com/hltfa…...
24校招总结
个人背景 本科:三本通信专业 硕士:B区双非计算机硕 今年2月签了东南沿海二线城市某公司C游戏服务端开发 我同学大部分都是去电网,大专老师,气象局事业编……就我这个是纯牛马了。 离收到Offer3个月了,前段时间参加…...
PHP APCu缓存使用与避坑
APCu 极简概括: PHP 的开源内存缓存扩展,类比Redis,但是一般都用Redis,所以APCu用的很少。官方文档:https://www.php.net/manual/zh/apcu.configuration.php解决问题:类比Redis做缓存组件,提升…...
mybatis xml
delete from t_enterprise_output_value where output_id IN #{outputId} 批量插入 功能:单个或批量插入数据,若数据已存在,则忽略 <insert id"saveBatchIgnoreInto" parameterType"java.util.List">insert igno…...
“不是我兄弟”!刘强东内部“狼性训话”流出!
今天,京东创始人刘强东5月24日的线上讲话流出。 在这次线上讲话中,刘强东首先宣布为全体采销员工涨薪20%—100%,随后进行了一番“狼性训话”。往期报道可戳:刘强东怒了:“不是我兄弟”! 刘强东在讲话中指…...
函数调用时长的关键点:揭秘参数位置的秘密
新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、默认参数的秘密 示例代码 二、关键字参数与位置参数的舞蹈 示例代码 总结 一、默认参…...
【数据分析面试】54.员工信息(HR)数据库搭建
题目 由于发展需求,进一步提高公司人员统筹管理的能力,公司决定要重新升级人力数据管理系统。 现在,你的任务是为公司重新设计和搭建一个员工信息数据库。 提示:考虑HR管理系统的功能,比如人员信息、入职时间、离职…...
通过JavaScript本地存储数据
文章目录 本地存储本地存储分类 - localStorage本地存储分类 - sessionStorage存储复杂数据类型解决方法 本地存储 数据存储在用户浏览器中设置、读取方便、甚至页面刷新都不丢失数据容量较大,sessionStorage和localStorage约5M左右 本地存储分类 - localStorage …...
CTF-web-攻防世界-3
1、inget (1)、进入网站,提示传入id值 (2)、用一些闭合方式,返回都一样。 (3)、尝试万能密码。获得flag 2、mfw (1)、页面没有什么特殊的异常,使用dirsearch进行目录扫描,有一些.git文件。看样子是.git文件泄露。 使用githa…...
【附代码案例】深入理解 PyTorch 张量:叶子张量与非叶子张量
在 PyTorch 中,张量是构建神经网络模型的基本元素。了解张量的属性和行为对于深入理解模型的运行机制至关重要。本文将介绍 PyTorch 中的两种重要张量类型:叶子张量和非叶子张量,并探讨它们在反向传播过程中的行为差异。 叶子张量与非叶子张…...
TypeScript 学习笔记(七):TypeScript 与后端框架的结合应用
1. 引言 在前几篇学习笔记中,我们已经探讨了 TypeScript 的基础知识和在前端框架(如 Angular 和 React)中的应用。本篇将重点介绍 TypeScript 在后端开发中的应用,特别是如何与 Node.js 和 Express 结合使用,以构建强类型、可维护的后端应用。 2. TypeScript 与 Node.js…...
Linux基础知识点总结!超详细
Linux 的学习对于一个IT工程师的重要性是不言而喻的,学好它是工程师必备修养之一。 Linux 基础 操作系统 操作系统Operating System简称OS,是软件的一部分,它是硬件基础上的第一层软件,是硬件和其它软件沟通的桥梁。 操作系统…...
中小学校活动怎样投稿给媒体报道宣传?
身为一名学校老师,同时承担起单位活动向媒体投稿的宣传重任,我深知每一次校园活动背后的故事,都承载着师生们的辛勤汗水与教育的无限可能。起初,我满怀着对教育的热情,希望通过文字传递校园的温暖与光芒,却在投稿的道路上遇到了前所未有的挑战。 最初,我选择了最传统的路径——…...
Python代码:十七、生成列表
1、题目 描述: 一串连续的数据用什么记录最合适,牛牛认为在Python中非列表(list)莫属了。现输入牛牛朋友们的名字,请使用list函数与split函数将它们封装成列表,再整个输出列表。 输入描述: …...
C++ 程序的基本要素
一 标识符 程序中变量、类型、函数和标号的名称称标识符。 a,b,name,int,char,main,void等。 系统已有的标识符称为关键字。 常见关键字 using,namespace,void,return; int,float,double,char,bool,signed,unsignex, long,short,const,true,false,sizeof if,else,for,do,whil…...
藏汉翻译工具有哪些?这三款工具简单好用
藏汉翻译工具有哪些?在全球化日益加剧的今天,语言交流成为连接不同文化、促进民族间沟通与理解的重要桥梁。藏汉翻译工具作为推动藏汉文化交流的得力助手,其在促进民族团结、增进相互理解方面的作用愈发凸显。本文将为您盘点市面上主流的藏汉…...
three.js官方案例webgl_loader_fbx.html学习
目录 1.1 添加库引入 1.2 添加必要的组件scene,camera,webrenderer等 1.3 模型加载 1.4 半球光 1.5 动画 1.6 换个自己的fbx模型 1.7 fbx模型和fbx动画关联 1.7 html脚本全部如下 1.8 fbx.js全部脚本如下 1.1 添加库引入 import * as THREE from three; import Stats …...
51单片机-实机演示(单多个数码管)
仿真链接: http://t.csdnimg.cn/QAPhx 目录 一.引脚位置 二.多个显示 三 扩展 一.引脚位置 注意P00 - >A ; 这个多个的在左边,右边的A到B是控制最右边那个单个的. 接下来上显示单个的代码 #include <reg52.h> #include <intrins.h> #define u…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
