一、进程 线程 协程
1.进程
一个运行的程序就是一个进程,没有运行的代码叫做程序。
进程是系统分配资源的最小单位,进程拥有自己独立的内存空间 相对独立,所以进程间的数据不共享,开销大。
2.线程
CPU调度执行的最小单元(程序真正执行的时候调用的是线程),也叫执行路径,依赖于进程存在,
一个进程最少存在一个线程,叫做主线程
而多个线程共享进程内存资源(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
一个进程中可以有一个或多个线程。一个线程只属于一个进程。
一个进程中的多个线程是一种竞争关系。
3.协程
协程类似于线程,基于线程,但是协程允许一个执行过程A中断,转而执行过程B,然后再执行过程A
优势:
协程理论上数量有无限个, 没有线程之间的切换动作,所以比较快;
没有锁机制, 因为所有协程都在一个线程中.
是一种用户态的轻量级线程,协程的调度完全由用户控制。
协程拥有自己的寄存器上下文和栈。 协程调度切换时,将寄存器上下文和栈保存到其他地方,
在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,
可以不加锁的访问全局变量,所以上下文的切换非常快。
二、多进程 多线程
1.多进程
(并行:两个人每个人同时处理一个任务)
多进程模块:multiprocessing ,CPU密集型、IO计算型可以使用多进程
模块导入:from multiprocessing import Process
Process创建进程
Process([group [, target [, name [, args [, kwargs]]]]])
target | 如果传递了函数的引用,可以让这个子进程就执行函数中的代码 |
---|---|
target | 如果传递了函数的引用,可以让这个子进程就执行函数中的代码 |
args | 给 target 指定的函数传递的参数,以元组的形式进行传递 |
kwargs | 给 target 指定的函数传递参数,以字典的形式进行传递 |
name | 给进程设定一个名字,可以省略 |
group | 指定进程组,大多数情况下用不到 |
常用方法:
start() | 启动子进程实例(创建子进程) |
---|---|
is_alive() | 判断进程子进程是否还在活着 |
terminate() | 不管任务是否完成,立即终止子进程Process 创建的实例对象的常用属性 |
name | 当前进程的别名,默认为 Process-N,N 为从 1 开始递增的整数 |
pid | 当前进程的 pid(进程号) |
一个进程的,生命周期中的几种状态:
出生:
就绪:start()
执行:
阻塞:sleep() / IO操作
死亡:正常结束 / 异常结束
import time
from multiprocessing import Process
def download1(name):
for i in range(1, 6):
print(f'{name}下载进度:{i * 20}%')
time.sleep(1)
def download2(name):
for i in range(1, 6):
print(f'{name}下载进度:{i * 20}%')
time.sleep(1)
if __name__ == '__main__':
# 创建进程 p1 p2
p1 = Process(target=download1, args=("图片",))
p2 = Process(target=download2, args=("音乐",))
# 开启进程
p1.start()
p2.start()
"""
音乐下载进度:20%
图片下载进度:20%
图片下载进度:40%
音乐下载进度:40%
图片下载进度:60%音乐下载进度:60%
图片下载进度:80%
音乐下载进度:80%
图片下载进度:100%音乐下载进度:100%
"""
程序理解:
主进程从main()开始执行 → p1.start()时,创建一个子进程
pi子进程中的代码与主进程相同,只是程序执行的开始是download1函数 → 主进程执行到
p2.start()时,同样赋值一份主进程代码从download2函数开始执行。
用进程方式来实现多任务耗费资源较大,因为一个进程就需要使用一份系统资源
——————————————————————
实现不同进程之间的通信
使用队列Queue:多进程间的共享队列 (锁 信号量处理系统已具备 )
模块导入:from multiprocessing import Queue
q = Queue(3) # 3表示只能存放3个数据
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
返回值q 是队列对象
工作机制:FIFO 先进先出
常用方法
qsize() | 对长 |
---|---|
put() | 向队列中存放数据,若队列已满,此方法将阻塞至有空间可用为止 |
put_nowait() | 不考虑其他直接入队 |
get() | 返回队列中一个数据 |
full() | 是否对满,若已满返回Ture |
empty() | 是否对空,若为空返回Ture |
———————————————————————
进程池
作用:多任务并行执行时,通过确定并行执行进程数量,管理任务执行过程
如若有100个任务需要处理,可直接使用Process创建100个子进程实现,但资源比较浪费。
也可以创建指定个数个子进程,如只创建10个子进程,让10个子进程重复执行任务这样就节约了资源
模块导入:from multiprocessing import Pool
同步::任务1 2,先执行任务1,在执行任务2
apply(func[, args[, kwds]])
同步操作,它会阻塞,直到结果就绪
异步::任务1 2,1 2两个任务之间互不影响,可同时执行
apply_async(func[, args[, kwds[, callback[, error_callback]]]])
异步操作,不会阻塞,可以指定回调函数
异步任务中,可以指定回调函数,回调函数可以接受参数,
参数的实际值来自目标任务函数的返回值
“”“
模拟实现一部电视剧的下载
“””
import random
import time
from multiprocessing import Pool
teleplay = [f'西部世界第{i}集' for i in range(1, 20)]
def download(name):
for i in range(1, 11):
print(f'{name}下载:{i * 10}%')
time.sleep(random.random())
return name
def alert(name):
print(f'{name}下载完成!')
"""
1 创建进程池
2 将下载任务都放入进程池中
3 关闭进程池 close
4 让进程池阻塞其他任务(让我先来) join
"""
if __name__ == '__main__':
pool = Pool() #由于没有设置数量 默认为 os.cup_count 系统最优
for s in teleplay:
pool.apply_async(func=download, args=(s,), callback=alert) #异步
pool.close()
pool.join()
"""
西部世界第1集下载:10%
西部世界第2集下载:10%
西部世界第3集下载:10%
西部世界第4集下载:10%
西部世界第4集下载:20%西部世界第1集下载:20%
西部世界第3集下载:20%
西部世界第4集下载:30% ...
"""
pool.apply(func=download, args=(s,)) #同步
"""
西部世界第1集下载:10%
西部世界第1集下载:20%
西部世界第1集下载:30%
西部世界第1集下载:40% ...
"""
——————————————————————————————
2.多线程
(并发:一个人处理两个任务,处理过程中,可能会出现两个任务交替的情况)
多线程模块:threading,IO密集型可以使用多线程
模块导入:from threading import Thread
Thread创建线程
Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None))
常用方法:
join() | 当前线程执行完后其他线程才会继续执行(可以阻塞其他的,让我自己先来) |
---|---|
setDaemon() | 当前线程设置成守护线程,主线程结束,子线程无论是否执行完毕,直接结束 ;注意:需要在子线程开启之前设置成守护线程,否则无效。 |
threading.currentThread() | 返回当前的线程变量 |
threading.enumerate() | 返回一个包含正在运行的线程list,正在运行是指线程启动后、结束前,不包括启动前和终止后的线程 |
threading.activeCount() | 与len(threading.enumerate) 相同;返回正在运行的线程数量 |
import time
from threading import Thread
def download1(name):
for i in range(1, 6):
print(f'{name}下载进度:{i * 20}%')
time.sleep(1)
def download2(name):
for i in range(1, 6):
print(f'{name}下载进度:{i * 20}%')
time.sleep(1)
if __name__ == '__main__':
# 1.创建线程
t1 = Thread(target=download1, args=('音乐',))
t2 = Thread(target=download2, args=('图片',))
# 2.开启线程
t1.start()
# t1.join()
t2.setDaemon(True) #t2 为守护线程
t2.start()
“”“
下载任务1进行:20%
下载任务2进行:10%
下载任务2进行:20%下载任务1进行:40%
下载任务2进行:30%下载任务1进行:60%
下载任务1进行:80%下载任务2进行:40%
下载任务1进行:100%下载任务2进行:50%
”“”
线程的创建方式:
基于类继承的方式实现自定义的线程类:
threading.Thread
import threading
import time
class Mythread(threading.Thread):
def __init__(self, num):
#必须要处理父类init方法
super().__init__()
self.num = num
def run(self):
for i in range(self.num):
print(f'i={i}, {threading.currentThread()}')
time.sleep(1)
#在run里添加调用其他函数 如 self.login()
def login(self):
...
if __name__ == '__main__':
# 创建一个实例对象,只能有一个线程,一个线程同一时刻只能执行一个函数
t1 = Mythread(5) #正常方式传递参数
t1.start()