一、多任务开发

1、概念:同一时间内执行多个任务

2、优点:充分利用CPU资源,提高程序的执行效率

3、执行方式

  • 并发:在一段时间内交替执行任务
  • 并行:始终有多个软件一起执行(多核 CPU)

4、实现方式

多进程开发 多线程开发
可以用多核 不能使用多核
资源开销大 资源开销小
稳定性强 稳定性弱

进程和线程的关系

  • 进程:操作系统进行资源分配的基本单位
  • 线程:CPU 资源调度的最小单位

程序运行至少有一个进程,一个进程默认有一个线程,进程里可以创建多个线程,线程依附进程

二、多进程开发

1. 创建子进程实例对象

  • 创建子进程实例对象:multiprocessing.Process(target=执行任务名)
  • 创建实例对象常用参数
参数 说明
group 指定进程组,默认为None,且目前不支持修改
target 执行任务名
name 进程名
args 以元组方式给执行任务传参,需要和执行任务形参的顺序保持一致
kwargs 以字典方式给执行任务传参,key值需要和形参名保持一致

获取当前进程名:multiprocessing.current_process()

2. 子进程对象常用方法和属性

  • 常用方法
方法 说明
start() 启动子进程实例(创建子进程)
join() 等待子进程执行结束,再继续向下执行
terminate() 不管任务是否完成,立即终止子进程
  • 常用属性
属性 说明
name 当前进程名称,默认为Process-N,N为从1开始递增的整数
daemon 是否守护主进程,默认为 False

3. 获取进程编号方法

  • 获取当前进程编号:os.getpid()
  • 获取当前父进程编号:os.getppid()

强制杀死子进程:os.kill(进程编号,9)

4. 多进程开发注意点

  • 进程之间的执行是无序的

  • 进程之间不共享全局变量:创建子进程会对主进程资源进行拷贝,也就是主进程的一个副本

  • 主进程会等待所有的子进程执行结束后再结束,若需要子进程跟随主进程结束,有两种方式:

    • 守护主进程:子进程对象.daemon = True
    • 销毁子进程:子进程对象.terminate()

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import multiprocessing
import time
import os

def dance(name,age):
# 获取和打印当前进程编号
dance_process_id = os.getpid()
print('dance_process_id:',dance_process_id,multiprocessing.current_process())
# 获取和打印当前进程的父进程编号
dance_process_parent_id = os.getppid()
print('dance_process的父进程id:', dance_process_parent_id)

for i in range(3):
print('跳舞',name,age)
time.sleep(0.2)
# 强制杀死进程
# os.kill(dance_process_id,9)

def sing(name,age):
sing_process_id = os.getpid()
print('sing_process_id:',sing_process_id,multiprocessing.current_process())
sing_process_parent_id = os.getppid()
print('sing_process的父进程id:', sing_process_parent_id)

for i in range(3):
print('唱歌',name,age)
time.sleep(0.2)

# Win 系统下会递归拷贝子进程,所以需要加上 main,判断是否是主模块
if __name__ == '__main__':

# 获取和打印主进程编号
main_process_id = os.getpid()
print('main_process_id:',main_process_id,multiprocessing.current_process())
# 创建子进程 dance,并使用元组传参,并设置进程名
dance_process = multiprocessing.Process(target=dance,args=('Jerry',15),name= 'dance')
# 创建子进程 sing,并使用字典传参
sing_process = multiprocessing.Process(target=sing,kwargs={'age':20,'name':'Tom'})
# 子进程 sing 开启守护主进程模式
# sing_process.daemon = True
print('dance_process:',dance_process,dance_process.name)
print('sing_process:',sing_process,dance_process.name)
# 启动子进程 dance
dance_process.start()
# 等待子进程 dance 执行完毕,再继续执行
dance_process.join()
# 启动子进程 sing
sing_process.start()
time.sleep(0.2)
# 销毁子进程 sing
sing_process.terminate()
exit()
  • 运行结果

img

三、多线程开发

1. 创建子线程实例对象

  • 创建子线程实例对象:threading.Thread(target=执行任务名)
  • 创建实例对象常用参数
参数 说明
group 指定线程组,默认为None,且目前不支持修改
target 执行任务名
name 线程名
args 以元组方式给执行任务传参,需要和执行任务形参的顺序保持一致
kwargs 以字典方式给执行任务传参,key值需要和形参名保持一致
daemon 是否守护主线程,默认为 False

获取当前进程名:threading.current_thread()

2. 子线程对象常用方法和属性

  • 常用方法
方法 说明
start() 启动子线程实例(创建子线程)
join() 等待子线程执行结束,再继续向下执行
  • 常用属性
属性 说明
name 当前进程名称,默认为Thread-N,N为从1开始递增的整数

3. 互斥锁

作用:保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题

缺点:会影响代码的执行效率,多任务改成了单任务执行;如果没有使用好容易出现死锁的情况

  • 创建锁对象:threading.Lock()
  • 上锁:锁对象.acquire()
  • 释放锁:锁对象.release()

4. 多线程开发注意点

  • 线程之间执行是无序的

  • 线程之间共享全局变量,但容易出现数据错误问题,需要线程同步:

    • 方式一:设置线程等待:子线程对象.join()
    • 方式二:互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作
  • 主线程会等待所有的子线程执行结束后再结束,若需要子线程跟随主线程结束,可以设置守护主进程:创建子线程示例对象时,设置 daemon 属性为 True

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import threading
import time
import os

# 定义全局变量
g_num = 0
# 创建锁
mutex = threading.Lock()

def dance(name,age):
# 上锁
mutex.acquire()
for i in range(3):
print('跳舞',name,age)
time.sleep(0.2)
global g_num
g_num += 1

# 打印全局变量
print('dance g_num:',g_num)
# 释放锁
mutex.release()


def sing(name,age):
# 上锁
mutex.acquire()
for i in range(3):
print('唱歌',name,age)
time.sleep(0.2)
global g_num
g_num += 1

# 打印全局变量
print('dance g_num:',g_num)
# 释放锁
mutex.release()

if __name__ == '__main__':

# 获取和打印主线程名
main_thread = threading.current_thread()
print('main_thread:',main_thread)
# 创建子线程 dance,并使用元组传参,并设置线程名,开启守护主线程模式
dance_thread = threading.Thread(target=dance,args=('Jerry',15),name='dance',daemon=True)
# 创建子线程 sing,并使用字典传参,开启守护主线程模式
sing_thread = threading.Thread(target=sing,kwargs={'age':20,'name':'Tom'},daemon=True)
print('dance_process:',dance_thread.name)
print('sing_process:',sing_thread.name)
# 启动子线程
dance_thread.start()
sing_thread.start()
time.sleep(2)
# 打印全局变量
print('g_num:',g_num)
  • 运行结果

img