参考资料:https://www.bilibili.com/video/BV1fz4y1D7tU?p=1&vd_source=9c227976fc64451bd98c5af8637d55e0
1. 多任务的介绍
多任务:同一时间执行多个任务,现在的windows就是一个多任务系统,其优势在于充分利用资源提升执行效率。其主要包括:并行和并发两种方式。
- 并发:在一段时间内交替执行多个任务。(如单核的计算机)
- 并行:真正意义的同时执行。(如多核计算机同时运行)
2. 进程的介绍
进程:进程(Process)是资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,通俗理解: 一个正在运行的程序就是一个进程。
利用多进程执行多任务的举例:
# hello.py
def func_a():
print('任务A')
def func_b():
print('任务B')
func_a()
func_b()
3. 使用多进程完成多任务
本节讲解如何使用 python 库 multiprocessing 实现多进程编程。主要包括一下三个步骤:
- 步骤1: 引入 multiprocessing 多进程库文件
- 步骤2: 利用 multiprocessing.Process 多进程库创建多进程子对象,对象的初始化包括以下三个参数:
target:参数为需要进行多进程处理的函数名;
args:需要进行多进程处理的函数参数,以元组的形式;
kwargs:需要进行多进程处理的函数参数,以字典的形式; - 步骤3:利用类对象的 start 成员函数执行多进程
# 步骤1: 引入 multiprocessing 多进程库文件
import multiprocessing
import time
import os
def func_a(name):
print('func_a 子进程编号: ', os.getpid()) # func_a 子进程编号: 33636
print('func_a 的父进程编号: ', os.getppid()) # func_a 的父进程编号: 5268
for _ in range(5):
print('任务A..., {}'.format(name))
time.sleep(1)
def func_b(name):
print('func_b 子进程编号: ', os.getpid()) # func_b 子进程编号: 7004
print('func_b 的父进程编号: ', os.getppid()) # func_b 的父进程编号: 5268
for _ in range(5):
print('任务B..., {}'.format(name))
time.sleep(1)
if __name__ == '__main__':
print('主进程编号: ', os.getpid()) # 主进程编号: 5268
# 步骤2: 利用 multiprocessing.Process 多进程库创建多进程对象,需要指定 target 参数为函数名
taskA = multiprocessing.Process(target=func_a, args=('Peter',)) # 使用 元组传参,顺序一致
taskB = multiprocessing.Process(target=func_b, kwargs={'name': 'Jackey'}) # 字典传参,key名字一致
# 步骤3:利用类对象的 start 成员函数执行多进程
taskA.start()
taskB.start()
'''
输出内容:可以观察到 A,B 是交替同时执行的
任务A..., Peter
任务B..., Jackey
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
'''
4. 获取进程编号
进程编号的作用:当程序中进程的数量越来越多时,如果没有办法区分主进程和子进程还有不同的子进程,那么就无法进行有效的进程管理,为了方便管理,实际上每个进程都是有自己编号的。可借助 os 库来查询进程或其父进程的编号。
import os
# 获取进程编号
os.getpid()
# 获取父进程的编号
os.getppid()
以上例代码为例,主进程是 main 函数,主进程创建了两个子进程,分别是 func_a 核 func_b,查询其父进程编号,与主进程一致。
5. 主进程与子进程的结束
当希望结束主进程时,默认情况下需要等待所有的子进程结束了才会结束主进程。当然,很多时候想要主进程结束后,所有子进程也同时自动销毁。这时需要设置守护主进程,达到该目的。具体可以通过以下实现:
子进程对象.daemon = True
import multiprocessing
import time
def subprocess():
# 子进程工作 10 * 0.2 = 2s
for i in range(10):
print('子进程活跃中....')
time.sleep(0.2)
if __name__ =="__main__":
subprocess_ = multiprocessing.Process(target=subprocess)
# 设置守护主进程
subprocess_.daemon = True
subprocess_.start()
# 主进程只进行 1s, 那么主进程会比子进程先结束
time.sleep(1)
print("主进程结束啦!!!")
'''
1. subprocess_.daemon = False, 主进程会等待子进程结束
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
主进程结束啦!!!
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
2. subprocess_.daemon = True, 主进程结束,子进程自动销毁
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
子进程活跃中....
主进程结束啦!!!
'''
6. 线程的介绍
首先回顾一下,进程的概念:进程(Process)是资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,通俗理解: 一个正在运行的程序就是一个进程。
那么进程和线程的关系是:线程是程序执行的最小单位,实际上进程只负责分配资源,而利用这些资源执行程序的是线程,也就说进程是线程的容器,一个进程中最少有一个线程来负责执行程序,同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
通俗来说,进程是一家公司,而线程是公司里面干活的打工人。所有的打工人共享公司的资源。
那么很容易解释为什么需要多线程,而不是多进程了。因为多进程相对于建立多家公司来分管某个业务,多家公司意味着可以使用更多的资源(多核并行),而多线程相当于一家公司雇佣众多廉价的打工人来分管业务。相比而言,多线程更加的节省资源(单核并发)。
7. 多线程的使用方法
在 python 中多线程与多进程的使用方法极其相似,多线程的实现通过 threading 库实现。主要包括一下三个步骤:
- 步骤1: 引入 threading 多线程库文件
- 步骤2: 利用 threading.Thread 多线程库创建多线程子对象,对象的初始化包括以下三个参数:
target:参数为需要进行多线程处理的函数名;
args:需要进行多线程处理的函数参数,以元组的形式;
kwargs:需要进行多线程处理的函数参数,以字典的形式;
daemon:设置是否守护主线程,类似于第5节; - 步骤3:利用类对象的 start 成员函数执行多线程
# 步骤1: 引入 threading 多线程库文件
import threading
import time
import os
def func_a(name):
# print('func_a 子线程编号: ', os.getpid())
# print('func_a 的父线程编号: ', os.getppid())
for _ in range(5):
print('任务A..., {}'.format(name))
time.sleep(1)
def func_b(name):
# print('func_b 子线程编号: ', os.getpid())
# print('func_b 的父线程编号: ', os.getppid())
for _ in range(5):
print('任务B..., {}'.format(name))
time.sleep(1)
if __name__ == '__main__':
# print('主线程编号: ', os.getpid())
# 步骤2: 利用 threading.Thread 多线程库创建多线程对象,需要指定 target 参数为函数名
taskA = threading.Thread(target=func_a, args=('Peter',)) # 使用 元组传参,顺序一致
taskB = threading.Thread(target=func_b, kwargs={'name': 'Jackey'}) # 字典传参,key名字一致
# 步骤3:利用类对象的 start 成员函数执行多线程
taskA.start()
taskB.start()
'''
输出内容:可以观察到 A,B 是交替同时执行的
任务A..., Peter
任务B..., Jackey
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
任务B..., Jackey
任务A..., Peter
'''
8. 线程的执行顺序
线程的执行顺序与创建顺序无关,其执行的顺序与CPU调度相关,通俗来说,其执行是无序的。可以通过以下例子证明:
import threading
import time
def task():
time.sleep(1)
current_thread = threading.current_thread()
print(current_thread)
if __name__ == '__main__':
# 创建 5 个子线程
for i in range(5):
subthread = threading.Thread(target=task)
subthread.start()
'''
<Thread(Thread-3, started 25756)>
<Thread(Thread-2, started 14956)>
<Thread(Thread-4, started 38088)>
<Thread(Thread-5, started 35376)>
<Thread(Thread-1, started 38608)>
'''