多道技术,进程,并发并行,同步异步,阻塞非阻塞,多进程,multiprocessing,join实现并发
Ⅰ 多道技术
- 目的:提高CPU利用率 降低程序等待时间
'''目前研究并发都是以计算机是单核的情况下:只有一个CPU'''
【一】串行
- 多个任务排队执行 总耗时就是多个任务完整时间叠加
【二】多道
- 利用空闲时间提前准备 缩短总的执行时间并且还能提高CPU的利用率
# 前提是只有一个CPU
多道技术:
1.空间上的复用
多个任务公用一套计算机硬件
2.时间上的复用
切换+保存状态
cpu在这两种状态下会被拿走
1.程序遇到IO操作 CPU自动切走运行其他程序
2.程序长时间占用CPU 系统发现后也会强行切走cpu 保证其他程序也可以使用
eg:做饭30min 洗衣服50min 烧水20min
串行总耗时:30min + 5omin + 20min
多道总耗时:50min
Ⅱ 进程理论(重要)
【一】什么是进程
- 进程其实就是一个正在运行的程序(活的)
【二】什么是程序
- 程序就是一堆没有被执行的代码和数据(死的)
【三】为什么有进程的概念
- 就是为了更加精确的描述出一些实际状态
【四】单任务和多任务
【1】单任务
- 一个单独的任务
- 一个时间段只能做一件事:铺床、吹头发、睡觉
【2】多任务
- 就是很多个任务
- 一个时间段可以做多件事
- 铺一回床 -- > 吹会头发 --> 铺回床 ---> 吹头发 --->睡觉、
【五】进程间的调度算法发展史
【1】先来先服务算法
谁先来的就想让谁做
- 优点:任务按照顺序依次完成
- 缺点:一个任务1min 1s --> 1min --> 1s
- 比较有利于长作业不利于短作业
【2】短作业优先调度
- 1min 30s 20s
- 谁时间最短就先执行谁
- 有利于短作业,但是不利于长作业
【3】时间片轮转法
时间片轮转法:先公平的将CPU分给每个人执行
多级反馈队列:根据作业长短的不同再合理分配CPU执行时间
'''目的就是为了能够让单核的计算机也能够做到运行多个程序'''
- 合理分配时间和空间资源
- 将一块时间切割成很多个小部分 1min ---> 60块 --> 1s
- 轮转法:日志轮转
【4】多级反馈队列
(1)理论
- 前面介绍的各种用作进程调度的算法都有一定的局限性
- 如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用
- 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。
(2)调度算法的实施过程
[1]为多个就绪队列设置优先级
- 应设置多个就绪队列,并为各个队列赋予不同的优先级。
- 第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。
- 该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。
- 例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
[2]新进程等待调用
-
当一个新进程进入内存后
-
- 首先将它放入第一队列的末尾,按FCFS原则排队等待调度。
-
当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;
-
- 如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;
- 如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
[3]按顺序调度队列
-
仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;
-
- 仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。
-
如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列)
-
- 则此时新进程将抢占正在运行进程的处理机
- 即由调度程序把正在运行的进程放回到第i队列的末尾
- 把处理机分配给新到的高优先权进程。
(3) 图片标识解释运行
Ⅲ 并发和并行(非常重要)
【一】串行
- 每个程序依次执行
【二】什么是并发
- 伪并行,看起来是同时在运行
- 一个CPU在不断的切换(多道技术:时间上的复用和空间上的复用)
举例说明:
-
''' 饭店里有很多桌客人(任务) 但是只有一个服务员(CPU) 如何让所有客人都感觉到被服务员服务着(CPU执行) 让服务员在多桌客人之间快速的来回切换并保存状态即可 让CPU在多个程序之间利用多道技术来回切换+保存状态 单核肯定能实现并发 但是不能实现并行!!! '''
【三】并行
- 多个任务在同时运行
单核计算机肯定不能实现并行
【四】高并发与高并行解释
高并发:
- 我们写的软件可以支持一个亿的并发量
- 一个亿的用户来了之后都可以感觉到自己被服务着
高并行:
- 我们写的软件可以支持一个亿的并行量
- 上述话语的言外之意就是计算机有一亿个CPU
【五】多道技术
- 内存中同时存入多道(多个)程序
- cpu从一个进程快速切换到另外一个
- 使每个进程各自运行几十或几百毫秒
- 这样,虽然在某一个瞬间
- 一个cpu只能执行一个任务
- 但在1秒内,cpu却可以运行多个进程
- 这就给人产生了并行的错觉,即伪并发
- 以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)
【六】总结
- 并行肯定算并发
- 单核的计算机肯定不能实现并行,但是可以实现并发。
Ⅳ 同步/异步/阻塞/非阻塞(非常重要)
【一】同步
- 在进行一个程序执行之后,必须等待当前程序执行完成才能继续下一个任务,在没有得到结果之前,该调用就不会返回
- 程序A跑起来 ---> 等待结果 ---> 开始程序B
【1】同步调用
- apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态)
【二】异步
- 在进行一个程序执行之后,没等待当前程序执行完成才能继续下一个任务
- 程序A跑起来---> 程序B --等待A的结果出现 -> 获取 A 的结果
【三】阻塞
【1】进程三状态
运行态 :程序被CPU执行着
就绪态 : 双击应用程序启动的过程中
阻塞态 : 上传文件需要读取文件数据的过程
'''
如果想要尽可能的提升程序执行效率
就要想办法让我们的程序一直处于就绪状态和运行状态(不要有IO操作)
'''
阻塞:阻塞态
非阻塞:就绪态,运行态
- 调用结果返回之前,当前程序一直会被挂起(夯住) IO阻塞
- 函数只有在得到结果之后才会将阻塞的线程激活。
- 有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。
- 对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
【1】阻塞调用
- 当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止。
【四】非阻塞
-
没有IO阻塞
-
非阻塞和阻塞的概念相对应
-
指在不能立刻得到结果之前也会立刻返回
-
同时该函数不会阻塞当前线程
-
【五】小结
【1】同步/异步
用来描述任务的提交方式
-
同步与异步针对的是函数/任务的调用方式
-
同步就是当一个进程发起一个函数(任务)调用的时候
-
- 一直等到函数(任务)完成,而进程继续处于激活状态。
-
而异步情况下是当一个进程发起一个函数(任务)调用的时候
-
- 不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
【2】阻塞/非阻塞
用来描述任务的执行状态
- 阻塞与非阻塞针对的是进程或线程
- 阻塞是当请求不能满足的时候就将进程挂起
- 而非阻塞则不会阻塞当前进程
【3】小总结
同步异步:用来描述任务的提交方式
阻塞非阻塞:用来描述任务的执行状态
# 上述两组属于两个不同概念 但是可以结合
同步阻塞:银行排队办理业务 期间不做任何事
同步非阻塞:银行排队办理业务 期间吃东西喝水 但是人还在队列中
异步阻塞:在椅子上坐着 但是不做任何事
异步非阻塞:在椅子上坐着 期间喝水吃东西办公 (程序运行的极致)
Ⅴ 进程的创建(了解)
- 创建和撤销进程的能力
- 创建进程是指启动某一个应用程序
- 撤销进程是指杀死指定的应用程序
'''
创建进程的方式有哪些
1.鼠标双击桌面一个应用图标
2.代码创建
创建进程的本质:在内存中申请一块内存空间用于运行相应的程序代码
'''
创建进程的本质:在内存中申请一块内存空间用于运行相应的程序代码
【一】系统初始化
- Windows里面用的任务管理器
- 随着开机,这些自带的继承也会被启动成功
【二】在进程中开设子进程
- 双击腾讯会议就是一个主进程 ---> 需要共享屏幕 --> 开了屏幕共享 子进程
【三】交互式请求
- 通过双击应用的快捷方式启动一个主进程
- 桌面上的快捷方式是启动入口
【四】批处理作业
- 只会在大型的工厂中
【五】不同系统的创建新进程方式的不同
【1】Windows
- 在windows中该系统调用是:
- CreateProcess,
- CreateProcess既处理进程的创建,也负责把正确的程序装入新进程
【2】linux : 内核就是Unix
- 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本
【六】进程的终止
【1】正常退出
- 自愿退出程序 --> 主动关闭腾讯会议
【2】出错退出
- 自愿执行:在执行程序过程中遇到某个错误,报了一个错,导致程序关闭
【3】严重错误
- 非自愿退出,遇到了严重系统错误
【4】被其他进程杀死
-
linux kill / pkill 杀死进程
-
Windows :任务管理器强制结束进程 --> 通过进程号杀死进程
【八】进程的状态(重点)
【1】三态模型
- 运行态 : 已经在使用过程中的状态
- 就绪态 : 双击应用程序启动的过程中
- 阻塞态 : 上传文件需要读取文件数据的过程
【2】五态模型
- 创建 : 双击应用程序启动的过程中 双击 LOL图标
- 就绪 : 应用程序已经准备好了 进入到 LOL客户端
- 执行 : 应用程序正在执行 正在打游戏
- 阻塞 : 应用处于等待状态 正在匹配队友
- 终止 : 应用程序结束 主动将LOL退出
【3】重点记住
# 重点记住
# 程序和进程的区别
# 并发/并行/串行
# 同步/异步/阻塞/非阻塞
# 进程的五态模型是哪五个
Ⅵ 多进程操作
多进程操作:如何用代码开设多个进程
【一】multiprocessing模块
-
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。
-
- Python提供了multiprocessing。
- multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
-
multiprocessing模块的功能众多:
-
- 支持子进程、通信和共享数据、执行不同形式的同步
- 提供了Process、Queue、Pipe、Lock等组件。
-
需要再次强调的一点是:
-
- 与线程不同,进程没有任何共享状
- 进程修改的数据,改动仅限于该进程内。
【二】Process类参数介绍
p = multiprocessing.Process()
'''
def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
*, daemon=None):
group : 参数表示一个组,但是我们不用
target:表示调用的对象 要让子进程完成的任务
name 子进程的名称
args : 元组类型,子进程完成的任务的函数的参数
kwargs 调用对象的字典
'''
【三】Process类的对象的方法介绍
p = multiprocessing.Process()
p 是实例后的对象
【1】p.start()
- 启动进程,并且会调用进程中 的 run 方法
"""
target:需要执行的子进程任务
self._target = target
def run(self):
'''
Method to be run in sub-process; can be overridden in sub-class
'''
if self._target:
self._target(*self._args, **self._kwargs)
"""
【2】p.run()
-
进程运行时的方法
-
真正的调用 自己传入的进程方法
【3】p.terminate():
-
强制终止进程p,不会进行任何清理操作
-
如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
-
如果p还保存了一个锁那么也将不会被释放,进而导致死锁
【4】p.is_alive():
- 如果p仍然运行,返回True
【5】p.join([timeout]):
主进程等待所有子进程结束后在结束主进程
-
主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。
-
timeout是可选的超时时间
-
需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
【四】Process类的对象的属性介绍
【1】p.daemon:守护进程
-
默认值为False
-
如果设为True,代表p为后台运行的守护进程
-
当p的父进程终止时,p也随之终止
-
并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
【2】 p.name:
- 进程的名称
【3】 p.pid:
- 进程的pid
【4】p.exitcode:
- 进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
【5】p.authkey:
-
进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。
-
这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
【五】开设多进程的两种方式
【1】在Windows上启动子进程
必须将启动入口放到 if name == 'main':
# 必须将启动入口放到 if __name__ == '__main__':
'''
Since Windows has no fork, the multiprocessing module starts a new Python process **and** imports the calling module.
If Process() gets called upon import, then this sets off an infinite succession of new processes (**or** until your machine runs out of resources).
This **is** the reason **for** hiding calls to Process() inside
**if name == "main"**
since statements inside this **if**-statement will **not** get called upon import.
'''
'''
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。
这是隐藏对Process()内部调用的原,使用if **name** == “**main** ”,
这个if语句中的语句将不会在导入时被调用。
'''
【2】multiprocessing使用
(1)导入模块
import multiprocessing
import random
import time
(2)创建子进程程序
def work(name):
print(f"{name} is starting \n")
sleep_time = random.randint(1, 6)
print(f"{name} is sleeping {sleep_time} s \n")
time.sleep(sleep_time)
print(f"{name} is ending \n")
(3)制作多进程的启动入口
1.方式一:同过multiprocessing的对象启动
def main_object():
# (1)实例化得到子进程对象
task_1 = multiprocessing.Process(
# target 就是需要启动的子进程的函数名
target=work,
# args 传入的位置参数,位置参数必须带 , 元组类型
args=("work_1",)
)
task_2 = multiprocessing.Process(
target=work,
kwargs={'name': 'work_2'}
)
# (2)启动子进程
# p.start()
task_1.start()
task_2.start()
class MyProcess(multiprocessing.Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"{self.name} is starting \n")
sleep_time = random.randint(1, 6)
print(f"{self.name} is sleeping {sleep_time} s \n")
time.sleep(sleep_time)
print(f"{self.name} is ending \n")
def main_class():
# 创建子进程一
task_1 = MyProcess(name='work_1')
task_2 = MyProcess(name='work_2')
#
task_1.start()
task_2.start()
(4)在主程序入口中启动当前子进程
if __name__ == '__main__':
start_time = time.time()
print(f"这是主进程 __main__ 开始 :>>>> \n")
# main_object()
main_class()
print(f"这是主进程 __main__ 结束 :>>>> \n")
end_time = time.time()
print(f'总耗时 :>>>> {end_time - start_time}s')
# 这是主进程 __main__ 开始 :>>>>
# 这是主进程 __main__ 结束 :>>>>
# work_1 is starting
# work_1 is sleeping 2 s
# work_2 is starting
# work_2 is sleeping 3 s
# work_1 is ending
# work_2 is ending
# 执行流程
# 先启动主进程
# 分别启动子线程
# 主进程结束
# 等待子进程分别结束
【六】子进程之间的数据是隔离的
import multiprocessing
# 多进程:两个甚至多个进程
# 多进程中的子进程之间的数据不共享
money = 9999
def change_money(name):
global money
print(f'{name} 原始的money :>>>> {money}')
# 在局部修改上面的 money 局部修改不可变数据类型
# 需要 提升变量等级 global
money = 8888
print(f'{name} 当前子进程修改后的money :>>>> {money}')
def main():
for i in range(10):
task = multiprocessing.Process(
target=change_money,
args=(i,)
)
task.start()
if __name__ == '__main__':
main()
【七】多进程实现服务端并发
import socket
client = socket.socket()
ip = '127.0.0.1'
port = 8802
addr = (ip, port)
client.connect(addr)
while True:
letter = input("请输入字母:>>>> ").strip()
client.send(letter.encode())
if letter == 'q':
client.close()
break
data = client.recv(1024)
print(f"这是来自服务单的数据 :>>>> {data.decode()}")
【1】未实现并发
import socket
server = socket.socket()
ip = '127.0.0.1'
port = 8802
addr = (ip, port)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(1)
conn, addr = server.accept()
while True:
data = conn.recv(1024)
if data.decode() == 'q':
conn.close()
print(f'这是来自客户端的数据 :>>>>{data.decode()}')
conn.send(data.decode().upper().encode())
【2】实现并发
import multiprocessing
import socket
server = socket.socket()
ip = '127.0.0.1'
port = 8802
addr = (ip, port)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(0)
def run(conn):
while True:
data = conn.recv(1024)
if data.decode() == 'q':
conn.close()
print(f'这是来自客户端的数据 :>>>>{data.decode()}')
conn.send(data.decode().upper().encode())
def main():
while True:
conn, addr = server.accept()
task = multiprocessing.Process(target=run, args=(conn,))
task.start()
if __name__ == '__main__':
main()
【八】join实现并发
【1】主进程结束子进程未结束
# 【一】multiprocessing使用
# 【1】导入模块
import multiprocessing
import random
import time
# 【2】创建子进程程序
def work(name):
print(f"{name} is starting \n")
sleep_time = random.randint(1, 6)
print(f"{name} is sleeping {sleep_time} s \n")
time.sleep(sleep_time)
print(f"{name} is ending \n")
# 【3】制作多进程的启动入口
# (1)方式一:同过multiprocessing的对象启动
def main_object():
# 这个生成式在创建多个子进程
task_list = [multiprocessing.Process(
# target 就是需要启动的子进程的函数名
target=work,
# args 传入的位置参数,位置参数必须带 , 元组类型
args=(f"work_{i}",)
) for i in range(5)]
# (2)启动子进程
task_list = [task.start() for task in task_list]
if __name__ == '__main__':
start_time = time.time()
print(f"这是主进程 __main__ 开始 :>>>> \n")
main_object()
print(f"这是主进程 __main__ 结束 :>>>> \n")
end_time = time.time()
print(f'总耗时 :>>>> {end_time - start_time}s')
# 这是主进程 __main__ 开始 :>>>>
# 这是主进程 __main__ 结束 :>>>>
# work_1 is starting
# work_1 is sleeping 5 s
# work_2 is starting
# work_2 is sleeping 4 s
# work_4 is starting
# work_4 is sleeping 4 s
# work_0 is starting
# work_0 is sleeping 2 s
# work_3 is starting
# work_3 is sleeping 2 s
# work_0 is ending
# work_3 is ending
# work_2 is ending
# work_4 is ending
# work_1 is ending
【2】主进程等待子进程结束(join串行)
# 【一】multiprocessing使用
# 【1】导入模块
import multiprocessing
import random
import time
# 【2】创建子进程程序
def work(name):
print(f"{name} is starting \n")
sleep_time = random.randint(1, 6)
print(f"{name} is sleeping {sleep_time} s \n")
time.sleep(sleep_time)
print(f"{name} is ending \n")
# 【3】制作多进程的启动入口
# (1)方式一:同过multiprocessing的对象启动
def main_object():
# 这个生成式在创建多个子进程
task_list = [multiprocessing.Process(
# target 就是需要启动的子进程的函数名
target=work,
# args 传入的位置参数,位置参数必须带 , 元组类型
args=(f"work_{i}",)
) for i in range(5)]
# (2)启动子进程
for task in task_list:
task.start()
task.join()
if __name__ == '__main__':
start_time = time.time()
print(f"这是主进程 __main__ 开始 :>>>> \n")
main_object()
print(f"这是主进程 __main__ 结束 :>>>> \n")
end_time = time.time()
print(f'总耗时 :>>>> {end_time - start_time}s')
# 这是主进程 __main__ 开始 :>>>>
# 这是主进程 __main__ 结束 :>>>>
# 并行变串行 ; 所有主进程等待所有子进程结束后才会结束
# 依次执行每一个子进程
# 并且是拿到上一个子进程的结果后才会执行下一个子进程
# 总耗时 :>>>> 22.509196758270264s
【3】主进程等待子进程结束(join并行)
# 【一】multiprocessing使用
# 【1】导入模块
import multiprocessing
import random
import time
# 【2】创建子进程程序
def work(name):
print(f"{name} is starting \n")
sleep_time = random.randint(1, 6)
print(f"{name} is sleeping {sleep_time} s \n")
time.sleep(sleep_time)
print(f"{name} is ending \n")
# 【3】制作多进程的启动入口
# (1)方式一:同过multiprocessing的对象启动
def main_object():
task_list = []
# 这个生成式在创建多个子进程
for i in range(5):
task = multiprocessing.Process(
# target 就是需要启动的子进程的函数名
target=work,
# args 传入的位置参数,位置参数必须带 , 元组类型
args=(f"work_{i}",)
)
task.start()
task_list.append(task)
# (2)启动子进程
for task in task_list:
task.join()
if __name__ == '__main__':
start_time = time.time()
print(f"这是主进程 __main__ 开始 :>>>> \n")
main_object()
print(f"这是主进程 __main__ 结束 :>>>> \n")
end_time = time.time()
print(f'总耗时 :>>>> {end_time - start_time}s')
# 这是主进程 __main__ 开始 :>>>>
# 这是主进程 __main__ 结束 :>>>>
# 总耗时 :>>>> 5.176285266876221s
# 并行
# 并且主进程等待所有子进程结束后再结束
# 耗时是最长的子进程的耗时
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY