【1.0】知识点小结(进程相关)

【1.0】知识点小结(进程相关)

参考笔记:Day 16 16.1 并发爬虫一、进程、线程以及协程 - Chimengmeng - 博客园 (cnblogs.com)

【一】什么是并发

  • 并发通常指系统或程序同时执行多个操作或任务的能力。
    • 在并发环境中,多个处理器、线程或进程可以同时执行不同的任务
    • 使得系统的响应时间更快,也提高了资源利用率。

并发简单来说就是能同时运行多个任务,但是多个任务不能同时进行,只能一个接一个的执行

【二】什么是并行

  • 并行指的是多个操作或任务在同一时刻同时执行的能力。

    • 在并行环境中,多个处理器、线程或进程可以协同工作
    • 同时完成不同的任务或对同一个大任务进行拆分并并行处理
    • 从而加速计算速度,提高系统性能。
  • 并行与并发的区别在于

    • 并发是指多个任务交替进行
      • 也就是说只有其中一个任务在执行;
    • 而并行则是多个任务同时进行
      • 即每个任务具有相同的优先级并且可以同时执行。

简单的说就是同时执行多个任务,在上一个任务结束以后迅速接上第二个任务,可以多个任务同时进行

【三】并发与并行之间的关系

  • 并发和并行都是多个任务的执行方式
    • 但它们的区别在于同一时间点上是否只有一个任务处于执行状态。
  • 具体来说
    • 当多个任务交替地轮流执行时
      • 我们称之为并发
    • 当多个任务同时执行时
      • 我们称之为并行。
  • 总之
    • 并发和并行是互相依存、联系紧密的概念
    • 两者都是解决多任务执行效率问题的常用方法,但实现方式有所不同。

PS:

​ 并行肯定是并发

​ 单核的计算机肯定不能实现并行,但是可以实现并发。

  • 多个任务交替执行 -----> 并发
  • 多个任务同时执行 -----> 并行

【四】什么是多道技术

【1】多道技术详解

  • 多道技术是一种以共享计算机系统的多个程序同时执行而著称的计算机操作系统设计技术。
    • 多道技术的实现可以增加系统资源(例如CPU)的利用率,从而提高系统的吞吐量和响应能力。
  • 在多道技术中,多个程序同时并发执行
    • 通过CPU时间片调度、进程切换等技术
    • 在不同的时间段内分配CPU资源给不同的程序,从而实现多个程序之间的并发执行。
    • 多道技术的主要特点是
      • 将资源动态地分配给多个程序,使每个程序都感觉到自己独占系统资源。

多道技术可以理解为

​ 在CPU执行线程A的处理部分的时候,将内存闲下来的资源用来缓存线程B的资源

​ 当CPU处理完A的处理部分的时候,马上处理B的处理部分,以此类推,从而大大减少各个线程之间的缓冲时间

  • 多道技术可以节省多个程序运行的总耗时

【2】空间上的复用和时间上的复用

  • 空间上的复用:
    • 在多道技术中,空间上的复用指的是如何将计算机系统的硬件资源(如内存、硬盘等)分配给多个程序或任务的管理方法。
    • 常见的空间上的复用技术包括虚拟内存、内存保护机制、I/O端口共享等。
    • 其中,虚拟内存是最常用的空间上的复用技术之一,其可以将主存中的内容自动地映射到磁盘文件中,从而实现对内存资源的更加合理地使用。
    • 通过虚拟内存技术,多道技术可以让多个程序之间共享物理内存,从而减少内存浪费并提高资源利用率。
  • 时间上的复用:
    • 在多道技术中,时间上的复用通常指的是操作系统中的进程调度机制,也称为作业调度。
    • 通过时间上的复用,不同的程序或任务可以轮流使用CPU,从而实现多个任务的并发执行。
    • 常见的进程调度算法包括先来先服务调度(FIFO)、最短作业优先调度(SJF)和基于时间片的轮转调度(Round-Robin)等。
    • 其中,基于时间片的轮转调度是最常用的时间上的复用技术之一,其可以将进程分成多个时间片并依次执行,从而让不同的进程轮流占用CPU短暂的时间,以实现并发执行。
    • 通过时间上的复用,多道技术可以提高多任务处理的吞吐量和响应能力。
  • 空间上的复用

    线程A和B共用一个CPU处理器

  • 时间上的复用

    线程A和线程B基于并行减少时间上的复用

【3】示例详解(切换节省时间)

例子需求:
	洗衣服 30min
	做饭   50min
	烧水   20min

(1)单道技术

一步一步进行
洗衣服 ----> 做饭 ----> 烧水
耗时 110min

(2)多道技术

同时进行
洗衣服(将衣服等加进去打开)
	烧水(等待洗衣服的同时,进行烧水)
		做饭(基于以上二者执行,空闲时间进行做饭)
耗时:50min

为什么是耗时 50min?

​ 在上述案例中,做饭是时间最长的任务,其他两个都是相对较短的,当开启其他任务,让其他任务后台进行的时候,我们去做时间最长的任务,当这个人任务结束以后,其他两个任务一定是结束了的。

​ 在计算机执行程序时,执行时间相对来说速度还是非常快的,时间一定是消耗在了时间最长的那个任务。所以我们取时间最长的那个执行时间。

【4】示例详解(保存任务状态)

边吃饭边玩游戏	
  • 我们在吃饭的同时玩游戏
    • 我们吃一口饭,玩一会游戏
  • 当我们不吃饭时
    • 游戏自动保存游戏进度
  • 当我们不玩游戏时
    • 我们吃的饭不会凭空多出来,还剩碗里的份量
  • 当我们再次玩游戏时
    • 游戏的任务进度还是上一次的进度
  • 当我们不玩游戏时
    • 吃的饭还是上次剩的那么多

【5】小结

(1)切换CPU分为两种情况

  • 当一个程序遇到IO操作的时候,操作系统会剥夺该程序的CPU执行权限
    • 提高了CPU的利用率
    • 不影响程序的执行效率

当我们打开QQ的时候,他的启动是需要先缓存数据再执行数据的

我们在打开微信的时候也是如此

基于以上,当QQ不需要CPU执行权限的时候,微信直接切换到自己的任务上

这时的总耗时是:各自的执行时间耗时

ps:操作系统的执行速度很快,别较真

  • 当一个程序长时间占用CPU的时候,操作系统也会剥夺该程序的CPU执行权限
    • 降低了程序的执行效率
    • 原本时间 + 切换时间

当我们长时间打开QQ,QQ占用CPU执行权限的时候,其他任务处在等待状态

这时,在QQ占用CPU期间,取其中一段时间的CPU权限处理其他的任务,处理完以后又马上切回到QQ进程

这时的总耗时是:QQ占用进程的总耗时 + 切换到其他进程运行的总耗时

ps:操作系统的执行速度很快,别较真

【五】程序与进程的区别

  • 程序(Program)
    • 是一系列指令的有序集合
    • 它是一个静态的概念
      • 即指描述指定任务所需操作的代码文件。
  • 进程(Process)
    • 是正在运行的程序在计算机上的实例
    • 是一个动态的概念
    • 它代表了程序在内存中的实际执行过程。
  • 进程包括了程序代码、数据和堆栈等信息,以及系统为该进程分配的其他资源,如寄存器、打开的文件、网络连接等等。
  • 每个进程都有自己的地址空间和系统资源
    • 因此进程之间是相互独立的
    • 它们不能直接访问另一个进程的数据或资源
    • 必须通过操作系统提供的进程间通信机制才能进行交互。
  • 因此,程序是静态的描述,而进程则是程序的动态执行过程的实例。

程序就是一堆躺在硬盘上的代码:是 "死的"

进程则表示程序正在执行的过程:是 "活的"

【六】什么是进程

  • 进程(Process)是计算机操作系统中的一个基本概念
    • 它表示正在运行的一个程序实例
    • 一个程序可以有多个进程
      • 每个进程都有自己独立的内存空间、寄存器、堆栈、文件描述符等系统资源。
  • 进程可以被看作是操作系统对正在运行的程序的抽象
    • 操作系统通过进程调度的方式来管理和控制多个进程的执行
    • 确保它们能够正确地共享系统资源并在不干扰彼此的情况下运行。
  • 在一个多任务的操作系统中
    • 操作系统必须对进程进行调度
    • 使得多个进程可以同时运行
    • 这就需要使用进程管理的各种算法(如进程调度算法)来合理地分配系统资源,保证系统的稳定性和效率。
  • 同时,操作系统还提供了一些进程间通信机制,使得不同进程之间可以相互协作完成更加复杂的任务。

【七】进程之间的调度问题

【1】先来先服务算法

  • 那个任务先来就先执行那个任务

  • 对长作业有利,对短作业无益

【2】短作业优先调度算法

  • 对短作业有利,对短作业益处不大

【3】时间片轮转法 + 多级反馈队列

  • 时间片
    • 将固定的时间切分成N多份,每一份就表示一个时间片

越往下:说明该任务需要的时间越长

越往下:说明任务执行的优先级越低

当第一个队列中出现了新的任务,那么CPU会立刻停止当前执行的任务,去执行新添加进来的第一层队列的任务

  • 比如在等待QQ启动的时间时,又点击了 微信 ,这时微信的优先级就高于QQ,会优先启动微信,再启动QQ

在Linux系统中可以给任务设定优先级,一次性分配几个时间片

  • 先将任务启动起来
    • 再去根据优先级调度CPU运行

【八】进程运行的三状态图

所有的程序要想被执行,必须先经历就绪态

线程的生命周期

创建:一个新的线程被创建,等待该线程被调用执行;

就绪:时间片已用完,此线程被强制暂停,等待下一个属于它的时间片到来;

运行:此线程正在执行,正在占用时间片;

阻塞:也叫等待状态,等待某一事件(如IO或另一个线程)执行完;

退出:一个线程完成任务或者其他终止条件发生,该线程终止进入退出状态,退出状态释放该线程所分配的资源。

【九】同步和异步

描述的是任务的提交方式

【1】同步

  • 任务提交之后,原地等待任务的反馈结果,等待的过程中不做任何事(干等)
    • 在程序中表现出来的感觉就是卡住了

【2】异步

  • 任务提交之后,不原地等待任务的反馈结果,而是直接去做其他事情,等待任务的反馈结果自动提交给调用者
    • 提交的任务的反馈结果如何获取?
      • 在程序中,任务的反馈结果会有一个异步回调机制

【十】阻塞和非阻塞

描述的是程序的运行状态

【1】阻塞

  • 阻塞态:也叫等待状态,等待某一事件(如IO或另一个线程)执行完;

【2】非阻塞

  • 就绪态:时间片已用完,此线程被强制暂停,等待下一个属于它的时间片到来;
  • 运行态:此线程正在执行,正在占用时间片;

【十一】异步非阻塞

基于同步和异步,阻塞和非阻塞

产生的最高效的一种组合:异步非阻塞

  • 程序的理想状态
    • 编写的程序永远处于就绪态或运行态

【十二】开启进程的两种方式

代码开启进程和线程的方式,代码书写基本一致

【1】基于process方法创建进程对象

'''
Windows 操作系统下
创建进程一定要在main内创建
因为 Windows 下创建进程类似于模块导入的方式
执行程序是从上向下依次执行代码

Linux 操作系统下
则是直接将代码拷贝一份
'''

# -*-coding: Utf-8 -*-
# @File : 01 开启进程 的两种方式 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24


# 方式一
from multiprocessing import Process
import time


def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is over')


if __name__ == '__main__':
    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    p = Process(target=task, args=('dream',))

    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    p.start()
    print(f'这是进程的主程序')

    # 这是进程的主程序
    # dream is running
    # dream is over

    # 原理:开启两个进程,一个进程在运行主程序的同时,另一个程序也在同时运行

【2】基于继承process的类创建进程对象

# 方式二:类的继承式
from multiprocessing import Process
import time


class MyProcess(Process):

    # 必须使用 run 函数
    def run(self):
        print(f'the process in beginning')
        time.sleep(3)
        print(f'the process in ending')


if __name__ == '__main__':
    p = MyProcess()
    p.start()
    print(f'this is the main process')
    
    # this is the main process
    # the process in beginning
    # the process in ending
    
    # 原理同方式一

【3】小结

创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去

一个进程对应在内存中就是一块独立的空间

多个进程对应在内存中就是多块独立的内存空间

进程与进程之间数据默认情况下是无法直接交互的,如果想交互可以借助第三方工具或模块

【十三】join方法

join 是让主程序的代码等待子进程代码运行结束后,再继续运行,不影响其他子进程的运行

【1】join方法演示 1.0 - 串行

将并行转为串行

# 方式一
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    for p in p_list:
        p.start()
        p.join()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')
    
    # 依次执行每一个子进程
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>3 执行完毕
    # 这是进程的主程序
    # 这是程序运行的总耗时:6.417520761489868

【2】非join方法 - 并行

# 方式二
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    for p in p_list:
        p.start()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')

    # 并行 ---- 先将进程启动起来  再 由 异步回调机制 拿到结果
    # 这是进程的主程序
    # 这是程序运行的总耗时:0.05299973487854004
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 执行完毕

【3】join方法升级 2.0 -并行

# 方式三
from multiprocessing import Process
import time


def task(task_name, task_time):
    print(f'这是进程:>>>>{task_name} 需要运行:>>>>{task_time}s')
    time.sleep(task_time)
    print(f'这是进程:>>>>{task_name} 执行完毕')


if __name__ == '__main__':
    # 初始化进行列表
    p_list = []

    # (1)创建进程对象
    # Process(target=被调用的任务名(进程名), args=(参数,))
    # args:里面的参数一定要用逗号隔开(容器类型无论里面有几个元素,哪怕只有一个元素,也一定要用逗号隔开)
    for i in range(1, 4):
        p = Process(target=task, args=(i, i,))
        p_list.append(p)

    start_time = time.time()
    # (2)开启进程
    # 告诉操作系统帮我们创建一个进程
    # (3)主进程等待子进程 p 运行结束后再继续往后执行
    # (3.1) 串行
    # 初始化任务列表
    task_list = []
    # 将所有任务添加到任务列表里
    for p in p_list:
        p.start()
        task_list.append(p)

    # 循环开始每一个任务
    for task in task_list:
        task.join()
    print(f'这是进程的主程序')
    print(f'这是程序运行的总耗时:{time.time() - start_time}')
    
    # join 方法的正确使用方法 : 多进程并行,总耗时为耗时最长的进程
    # 这是进程:>>>>1 需要运行:>>>>1s
    # 这是进程:>>>>2 需要运行:>>>>2s
    # 这是进程:>>>>3 需要运行:>>>>3s
    # 这是进程:>>>>1 执行完毕
    # 这是进程:>>>>2 执行完毕
    # 这是进程:>>>>3 执行完毕
    # 这是进程的主程序
    # 这是程序运行的总耗时:3.323244571685791

【十四】进程之间数据相互隔离

每一个子进程之间的数据是相互隔离的,在执行子进程代码时,只修改自己子进程内的数据,不会影响到其他的子进程

# -*-coding: Utf-8 -*-
# @File : 03 进程之间数据相互隔离 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process

money = 999


def func_name():
    global money  # 局部修改全局变量
    money = 99
    print(f'这是子进程中的数据:>>>>{money}')


if __name__ == '__main__':
    print(f'这是原始数据:>>>>{money}')
    p = Process(target=func_name)
    p.start()
    print(f'这是start后的数据:>>>>{money}')
    p.join()
    print(f'这是join后的数据:>>>>{money}')

    # 每一个子进程 只修改 自己子进程中的数据,而不会影响到其他的子进程
    # 这是原始数据:>>>>999
    # 这是start后的数据:>>>>999
    # 这是子进程中的数据:>>>>99
    # 这是join后的数据:>>>>999
posted @ 2023-06-25 08:31  Chimengmeng  阅读(11)  评论(0编辑  收藏  举报
/* */