多任务 同步 异步

1. 阻塞 非阻塞

阻塞:阻塞状态指程序未得到所需计算资源时被挂起的状态(无法继续往下)。或者程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。

常见的阻塞形式有:网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。

非阻塞:程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的。

2.同步 异步

2.1概念

同步和异步关注的是消息通信机制

同步就是调用方必须等待调用结果返回才能继续执行后续操作。
异步就是调用方不必等待结果返回,而是通过状态、通知或回调函数来处理调用结果。

同步和异步也可以指两个线程的运行方式。同步是指两个线程的运行是相关的,其中一个线程要阻塞等待另外一个线程的运行。异步是指两个线程毫无相关,自己运行自己的。

举个例子,如果你去商城买手机,你看上了一款手机,跟店家说你要这款手机,他就去仓库拿货,你得在店里等着,不能离开,这叫做同步。如果你在京东下单,下单完成后你就可以做其他事情(追剧、打王者、lol)等货到了去签收就ok了.这就叫异步¹。

总结
同步:执行一个操作后,必须等待结果,才能继续执行后续操作。
异步:执行一个操作后,可以去执行其他操作,然后等待通知再回来执行刚才没执行完的操作。

同步调用和异步调用
同步调用:函数和被调函数无法同时运行。(有阻塞产生)
异步调用: 函数和被调函数可以同时运行。(无阻塞产生)

2.同步异步优缺点

同步和异步的优缺点可以从不同的角度来分析。根据网上的一些资料,我可以给你这样的总结:

  • 从执行效率和时间上看,同步的执行效率会比较低,耗费时间,但有利于我们对流程进行控制,避免很多不可掌控的意外情况;异步的执行效率高,节省时间,但是会占用更多的资源,也不利于我们对进程进行控制¹²³⁴。
  • 从结果处理和异常捕获上看,同步流程对结果处理通常更为简单,可以就近处理,并且可以很容易捕获、处理异常;异步流程对结果的处理始终和前文保持在一个上下文内,并且需要通过状态、通知或回调函数来处理结果和异常²⁴。
  • 从使用场景上看,同步适合于不涉及共享资源,或对共享资源有写操作,或有时序上的严格关系,或需要原子操作的情况;异步适合于涉及IO操作等耗时操作,或对共享资源只读,或没有时序上的严格关系,或可以通过其他方式控制原子性的情况²⁴。

源:
(1) 同步和异步的区别及优缺点 - CSDN博客. https://blog.csdn.net/zf2014122891/article/details/84557098.
(2) 同步和异步的区别和优缺点 - CSDN博客. https://blog.csdn.net/hasijingsi/article/details/105285426.
(3) 同步和异步的区别及优缺点 通俗理解 - CSDN博客. https://blog.csdn.net/chenhuanqiangnihao/article/details/113864782.
(4) 同步和异步的区别及优缺点 - 大木瓜 - 博客园. 同步和异步的区别及优缺点 - 大木瓜 - 博客园.
(1) 同步(Synchronous)和异步(Asynchronous) - 小cai一碟 - 博客园. 同步(Synchronous)和异步(Asynchronous) - 小cai一碟 - 博客园.
(2) 同步与异步的区别(一看则懂) - CSDN博客. https://blog.csdn.net/hong521520/article/details/106671930.
(3) android开发中的同步和异步区别的理解 - CSDN博客. https://blog.csdn.net/shanglizhangrui/article/details/82899621.


1.多任务编程

1.1 多任务

一种同时执行多个任务或处理多个工作的能力
面对的问题:
例如并发访问共享资源可能引发竞态条件和数据一致性问题,需要采取合适的同步机制来解决。
此外,调度算法的设计和任务切换的开销也需要考虑。

1.2 多任务编程

指在编程中同时执行多个任务或线程。它可以提高程序的效率和响应能力,同时也可以利用多个处理器或核心的能力。
在实际开发中,Python 多任务编程可以通过以下三种形式实现
1.进程: 可以在操作系统级别同时运行多个独立的进程。
特点:每个进程拥有独立的内存空间和资源,可以实现更高的隔离性。

2.线程: 它可以在同一个程序内同时运行多个线程,每个线程负责执行不同的任务。
问题:多线程编程需要注意线程安全问题,比如访问共享资源时需要使用锁来保证数据的一致性

3.协程: 一种轻量级的多任务编程技术,它可以在同一个线程中实现多个任务的切换和调度。
协程通过yield语句和生成器函数实现任务的暂停和恢复,避免了线程切换的开销并减少了锁的使用。
应用场景:协程常用于异步编程场景,比如网络编程和IO密集型任务。

1.2.1多任务【进程】编程

进程: 一个正在运行的 程序或者软件 就是一个进程,它是 操作系统进行资源分配的基本单位 ,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。

特点:

  • 多个进程交替运行
  • 因为进程是程序执行的最小资源分配单位,当一个子进程被创建时,子进程会复制父进程的资源,形成一个独立的空间,所以 多个进程之间的数据是独立不共享的

python中使用multiprocessing实现多任务进程编程:
import multiprocessing

(1)创建进程

multiprocessing 模块使用 Process 类创建进程实例对象,实现进程任务的创建。

#  task1是方法名,不带括号哦
multiprocessing.Process(target=task1, name="myprocess1")

说明: target的参数是函数名,不带括号的

(2)启动进程

import multiprocessing
import time

# 跳舞任务
def task1():
    for i in range(5):
        print("跳舞中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建进程
    p1 = multiprocessing.Process(target=task1, name="myprocess1")
    # 启动进程
    p1.start()

(3)获取当前进程

multiprocessing.current_process() 可以获取当前进程

import multiprocessing
def task1():
    print(multiprocessing.current_process())
def task2():
    print(multiprocessing.current_process())

if __name__ == '__main__':
    p1 = multiprocessing.Process(target=task1, name="myprocess-1")
    p2 = multiprocessing.Process(target=task2)
    p1.start()
    p2.start()
---------------------------输出结果-------------------------------------------------------------------
<Process name='myprocess-1' parent=1472 started>
<Process name='Process-2' parent=1472 started> #此处name是默认的

说明:parent主程序main函数

(4)获取进程名

# 进程对象的 name 属性可以获取进程的名称
import multiprocessing

def task1():
    print(multiprocessing.current_process().name)

if __name__ == '__main__':
    print(multiprocessing.current_process().name)# MainProcess
    p1 = multiprocessing.Process(target=task1, name="myprocess-1")
    p1.start()
----------------------------------输出结果-----------------------------------------------
MainProcess
myprocess-1

(5)获取进程ID

每一个进程产生时,操作系统都分为进程分配一个ID编号,可以通过 os 模块中的方法获取进程的ID。

  • os.getpid() 获取当前进程ID;
  • os.getppid() 获取当前进程的父进程的ID

(6)进程任务函数传参

在创建进程对象的时候,为进程任务函数传递参数,可以使用两种方式为任务函数传参。

  • args: 使用可变位置参数形式传参–传递元组;
  • kwargs: 使用可变关键字参数形式传参–传递字典;
import multiprocessing
import time
def task(n, msg):
    for i in range(n):
        print(multiprocessing.current_process().name, f"打印第 {i+1} 次 {msg}")
        time.sleep(0.2)

if __name__ == '__main__':
    # 使用可变位置参数传参
    p1 = multiprocessing.Process(target=task, args=(10, "Python"))
    # 使用可变关键字参数传参
    p2 = multiprocessing.Process(target=task, kwargs={"n": 10, "msg": "Hogwarts"})
    p1.start()
    p2.start()

(7)进程 同步

join() 方法用来将子进程添加到当前进程之前执行,直到子进程执行结束后,当前进程才会继续向下执行。

原理: 多个进程间的代码在运行时是交替执行的,如果使用 join() 方法后,当前进程会进入到阻塞状态,等待子进程结束后,解除阻塞状态,继续执行当前进程。——即为现在是任务同步执行(非异步)

=》使用 join() 方法后,可使多进程的异步执行变成同步执行, 过多使用会使程序效率变低。

异步与同步执行是指在计算机编程中,程序或函数的执行方式。它们之间的主要区别在于程序等待某个操作完成的方式。

  • 同步执行:同步执行是指程序按照顺序执行,每个操作都必须在下一个操作开始之前完成。这意味着程序在等待某个操作完成时会停止执行其他任务。
  • 异步执行:异步执行是指程序在执行某个操作时,不会停下来等待该操作完成,而是继续执行其他任务。当异步操作完成时,程序会通过回调函数或其他方式通知结果。
import multiprocessing
import time
def task(n, msg):
    for i in range(n):
        print(multiprocessing.current_process().name, f"打印第 {i+1} 次 {msg}")
        time.sleep(0.2)

if __name__ == '__main__':
    # 使用可变位置参数传参
    p1 = multiprocessing.Process(target=task, args=(2, "Python"))
    # 使用可变关键字参数传参
    p2 = multiprocessing.Process(target=task, kwargs={"n": 2, "msg": "Hogwarts"})
    print('main run..1')
    p1.start()
    p1.join()
    p2.start()
    print('main run..2')
----------------------------------输出结果-----------------------------------------------
main run..1
Process-1 打印第1次python
Process-1 打印第2次python
main run..2
Process-2 打印第1次hogwarts
Process-2 打印第2次hogwarts

说明:如果没有join方法,正常main函数里(除了子程序p1、p2相关语句)语句都是在子程序之前运行的
——这种理解不对,实际上是主程序和子程序p1、p2是同时运行的(交替运行的),加了join后会阻塞主程序

import multiprocessing
import time
def task(n, msg):
    for i in range(n):
        print(multiprocessing.current_process().name, f"打印第 {i+1} 次 {msg}")
        time.sleep(0.2)

if __name__ == '__main__':
    # 使用可变位置参数传参
    p1 = multiprocessing.Process(target=task, args=(2, "Python"))
    # 使用可变关键字参数传参
    p2 = multiprocessing.Process(target=task, kwargs={"n": 2, "msg": "Hogwarts"})
    print('main run..1')
    p1.start()
    p2.start()
    print('main run..2')
----------------------------------输出结果-----------------------------------------------
main run..1
main run..2
Process-1 打印第1次python
Process-1 打印第2次python
Process-2 打印第1次hogwarts
Process-2 打印第2次hogwarts

(8)守护进程

比较:

  • 正常流程:多进程在执行时,父进程会等待子进程执行结束才会结束。
  • 进程同步-join:父进程里执行到子进程,父进程阻塞,子进程执行,子进程执行完后父进程才继续执行。
  • 守护进程:多进程在执行时,父进程结束时候强制子进程结束。

守护进程: 如果需要子进程在父进程执行结束后就结束执行,无论子进程是否执行完毕,可以将子进程设置为守护进程。
比如:只有开启企业微信后,才可以使用企业微信的会议功能,当企业微信退出时,会议也会随之退出。

设置守护进程方式有两种:

  • 使用 子进程对象.daemon = True 在子进程启动前将子进程设置为守护进程。
  • 使用 子进程对象.terminate() 在主进程退出前手动将子进程结束。设置子进程为守护进程

a.设置子进程为守护进程

import multiprocessing
import time
def task(n, msg):
    for i in range(n):
        print(multiprocessing.current_process().name, f"打印第 {i+1} 次 {msg}")
        time.sleep(0.2)

if __name__ == '__main__':

    p1 = multiprocessing.Process(target=task, args=(10, "Python"))
    p2 = multiprocessing.Process(target=task, kwargs={"n": 10, "msg": "Hogwarts"})
    # 在子进程启动前将子进程设置为守护进程,当主进程结束后,无论子进程是否执行完都要结束
    p1.daemon = True
    p1.start()
    p2.start()
    # 主进程睡1秒。。。
    time.sleep(1)
    print("main")
-----------------------------输出结果----------------------------------------------------
Process-1 打印第 1 次 Python
Process-2 打印第 1 次 Hogwarts
Process-1 打印第 2 次 Python
Process-2 打印第 2 次 Hogwarts
Process-1 打印第 3 次 Python
Process-2 打印第 3 次 Hogwarts
Process-1 打印第 4 次 Python
Process-2 打印第 4 次 Hogwarts
Process-1 打印第 5 次 Python
Process-2 打印第 5 次 Hogwarts
main
Process-2 打印第 6 次 Hogwarts
Process-2 打印第 7 次 Hogwarts
Process-2 打印第 8 次 Hogwarts
Process-2 打印第 9 次 Hogwarts
Process-2 打印第 10 次 Hogwarts

Process finished with exit code 0

b.手动杀死子进程

import multiprocessing
import time
def task(n, msg):
    for i in range(n):
        print(multiprocessing.current_process().name, f"打印第 {i+1} 次 {msg}")
        time.sleep(0.2)

if __name__ == '__main__':

    p1 = multiprocessing.Process(target=task, args=(10, "Python"))
    p2 = multiprocessing.Process(target=task, kwargs={"n": 10, "msg": "Hogwarts"})
    p1.start()
    p2.start()
    time.sleep(1)
    # 手动杀死子进程2
    p2.terminate()
    print("main")
-----------------------------输出结果-------------------------------------
Process-1 打印第 1 次 Python
Process-2 打印第 1 次 Hogwarts
Process-1 打印第 2 次 Python
Process-2 打印第 2 次 Hogwarts
Process-1 打印第 3 次 Python
Process-2 打印第 3 次 Hogwarts
Process-1 打印第 4 次 Python
Process-2 打印第 4 次 Hogwarts
Process-1 打印第 5 次 Python
Process-2 打印第 5 次 Hogwarts
main
Process-1 打印第 6 次 Python
Process-1 打印第 7 次 Python
Process-1 打印第 8 次 Python
Process-1 打印第 9 次 Python
Process-1 打印第 10 次 Python