多任务(进程、线程、协程)编程

一、多任务编程

1.1 多任务编程

  • 多任务是指同时执行多个任务或处理多个工作的能力。在日常生活和工作中,多任务是一种普遍存在的情况。

  • 在计算机领域,多任务也是一个重要的概念。操作系统和软件可以利用计算机的处理能力,同时执行多个任务或进程。这样可以提高计算机的效率和资源利用率。多任务在操作系统中通过任务调度算法来实现,以确定每个任务的执行时间和优先级。

  • 在计算机编程中,多任务编程可以通过多线程、多进程或协程等方式实现。每个任务或线程可以并行或交替执行,实现并发处理。多任务编程可以提高程序的性能和响应能力,并充分利用多核处理器或多处理器系统的性能优势。

  • 然而,多任务编程也面临一些挑战和问题,例如并发访问共享资源可能引发竞态条件和数据一致性问题,需要采取合适的同步机制来解决。此外,调度算法的设计和任务切换的开销也需要考虑。

1.2 多任务编程

  • 多任务编程是指在编程中同时执行多个任务或线程。它可以提高程序的效率和响应能力,同时也可以利用多个处理器或核心的能力。

  • 在实际开发中,Python 多任务编程可以通过以下三种形式实现:

    • 线程
    • 进程
    • 协程
  • 多线程是最常见的一种多任务编程技术,它可以在同一个程序内同时运行多个线程,每个线程负责执行不同的任务。多线程编程能够充分利用多核心处理器的性能优势,提高程序的并发能力。然而,多线程编程需要注意线程安全问题,比如访问共享资源时需要使用锁来保证数据的一致性。

  • 另一种常见的多任务编程技术是多进程编程,它可以在操作系统级别同时运行多个独立的进程。每个进程拥有独立的内存空间和资源,可以实现更高的隔离性。

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

  • 总结起来,多任务编程是一种提高程序并发能力和效率的编程技术,可以通过多线程、多进程或协程等方式实现。在选择多任务编程技术时,需要根据实际需求和情况综合考虑各种因素,比如性能、并发性、开发难度和可维护性等。

二、多任务进程编程

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

  • Python中使用multiprocessing模块实现多任务进程编程。

import multiprocessing

2.1 创建进程

  • multiprocessing模块使用Process类创建进程实例对象,实现进程任务的创建。
Process([group [, target [, name [, args [, kwargs]]]]])
  • 参数说明:
参数 描述
group 指定进程组,目前只能使用None
target 执行的目标任务名
name 进程名字
args 以元组方式给执行任务传参
kwargs 以字典方式给执行任务传参

2.2 启动进程

  • 进程对象创建成功后,需要启动进程才会开始执行。
p1.start()

2.3 获取当前进程

  • multiprocessing.current_process() 可以获取当前进程。
import multiprocessing as mp
import time

# 定义任务函数
def task1():
    for i in range(10):
        print(mp.current_process(), "Task 1 Run ...")
        time.sleep(0.2)

def task2():
    for i in range(10):
        print(mp.current_process(), "Task 2 Run ...")
        time.sleep(0.2)

# 创建任务函数
def creat_task():
    p1 = mp.Process(target=task1, name="myProcess-1")
    p2 = mp.Process(target=task2)

    p1.start()
    p2.start()
    print("main", mp.current_process())

if __name__ == '__main__':
    creat_task()


2.4 获取进程名

  • 进程对象的 name 属性可以获取进程的名称。
import multiprocessing as mp
import time

# 定义任务函数
def task1():
    for i in range(10):
        print(mp.current_process(), "Task 1 Run ...")
        time.sleep(0.2)

def task2():
    for i in range(10):
        print(mp.current_process(), "Task 2 Run ...")
        time.sleep(0.2)

# 创建任务函数
def creat_task1():
    p1 = mp.Process(target=task1, name="myProcess-1")
    p2 = mp.Process(target=task2)

    p1.start()
    p2.start()
    print("main", mp.current_process())

def task():
    for i in range(10):
        if mp.current_process().name == "Process-1":
            print(mp.current_process().name, "TASK1 - RUN ...")
        else:
            print(mp.current_process().name, "TASK2 - RUN ...")
        time.sleep(0.2)

def creat_task2():
    p1 = mp.Process(target=task)
    p2 = mp.Process(target=task)

    p1.start()
    p2.start()
    print("main", mp.current_process())

if __name__ == '__main__':
    # creat_task()
    creat_task2()

2.5 获取进程ID

  • 每一个进程产生时,操作系统都会为进程分配一个ID编号,可以通过 os 模块中的方法获取进程的ID。
    • os.getpid():获取当前进程ID;
    • os.getppid():获取当前进程的父进程ID。
import multiprocessing as mp
import time
import os

def task():
    for i in range(10):
        if mp.current_process().name == "Process-1":
            print(f"{mp.current_process().name}_ID", os.getpid())
            print(f"{mp.current_process().name}Parent_ID", os.getppid())
            print("*" * 30)
        else:
            print(f"{mp.current_process().name}_ID", os.getpid())
            print(f"{mp.current_process().name}Parent_ID", os.getppid())
            print("*" * 30)
        time.sleep(0.2)

def creat_task2():
    p1 = mp.Process(target=task)
    p2 = mp.Process(target=task)

    p1.start()
    p2.start()
    print("main", mp.current_process())

if __name__ == '__main__':
    creat_task2()

2.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=(2, "Python哈哈"))
    # 使用可变关键字参数传参
    p2 = multiprocessing.Process(target=task, kwargs={"n": 3, "msg": "今日是好日。"})
    p1.start()
    p2.start()

2.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": 3, "msg": "今日是好日。"})
    p1.start()
    p1.join()
    print("main run ...")
    p2.start()
    p2.join()

2.8 守护进程

  • 多进程在执行时,父进程会等待子进程执行结束后再结束。

  • 如果需要子进程在父进程执行结束后,就执行结束,无论子进程是否执行完毕,可以将子进程设置为守护进程。

  • 守护进程有两种方式:

  1. 设置子进程为守护进程:使用子进程对象.daemon = True在子进程启动前将子进程设置为守护进程;
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": "今日是好日。"})
    p1.daemon = True
    p1.start()
    p2.start()
    time.sleep(1)
    print("main")

  1. 手动杀死子进程:使用子进程对象.terminate()在主进程退出前手动将子进程结束。
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": "今日是好日。"})
    p1.start()
    p2.start()
    time.sleep(1)
    p2.terminate()
    print("main")

2.9 进程间不共享全局变量

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

# 定义全局变量
g_list = list()

# 添加数据的任务
def add_data():
    for i in range(5):
        g_list.append(i)
        print("add: ", i)
        time.sleep(0.2)

    print("add_data: ", g_list)

def read_data():
    print("read_data: ", g_list)

if __name__ == '__main__':
    add_data_process = multiprocessing.Process(target=add_data)
    read_data_process = multiprocessing.Process(target=read_data)

    add_data_process.start()
    add_data_process.join()
    read_data_process.start()

    print("main: ", g_list)

三、多任务线程编程

  • 线程是指在一个程序中执行的一段指令流。
  • 在操作系统中,线程