多道技术,进程,并发并行,同步异步,阻塞非阻塞,多进程,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
    # 并行
    # 并且主进程等待所有子进程结束后再结束
    # 耗时是最长的子进程的耗时

posted on   silence^  阅读(41)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示