多线程:theading,CPU不会等待IO完成(并发)
多进程:multiprocessing,利用多核CPU的能力,真正的并行执行任务
异步IO:asyncio,在单线程中利用CPU和IO同时执行的原理,实现函数的异步执行
在使用并行编程时:
使用Lock对数据源加锁,防止冲突访问
使用Queue实现不同线程或进程之间的数据通信,实现生产者-消费者模式。
Queue是python中的线程安全的队列实现,提供了一个适用于多线程先进先出的数据结构(队列),用来在生产者和消费者线程之间的信息传递。
maxsize--用于定义队列中的数量上限(int)一旦达到上限,插入会被阻塞,直至队列中的数据被消费掉,如果maxsize<=0,队列大小无上限
FIFO -- First in First Out,先进先出。Queue.Queue(maxsize=0)
LIFO--Last in First Out,后进先出。 Queue.LifoQueue(maxsize=0)
优先级队列。Queue.PriorityQueue(maxsize=0)
队列的代码详解:
import queue # python2引入包使用 import Queue
q1 = queue.Queue() # FIFO--先进先出
q2 = queue.LifoQueue() # LIFO--后进先出
q3 = queue.PriorityQueue() # 优先级队列
# 生产者
for i in range(5):
q1.put(i)
q2.put(i)
l_demo = [2,4,1,6,3]
for l in l_demo:
q3.put(l)
# 消费者
def customer(q):
ls = []
while not q.empty(): # 判断列表是否为空,为空返回True,不为空返回False
ls.append(q.get())
print(ls)
print('===FIFO--先进先出===')
customer(q1)
print('===LIFO--后进先出===')
customer(q2)
print('===优先级队列===')
customer(q3)
结果如下
使用线程池Pool(或进程池Pool),简化线程(或进程)任务
使用subprocess启动外部程序的进程。
subprocess 模块可以启动一个新进程,并连接到它们的输入/输出/错误管道,获取进程执行的结果(状态码)。其缺点在于默认提供的父子进程之间的通信手段只有管道,同时创建的子进程(Popen)专门用来执行外部的程序或者是命令
CPU密集型
又叫做计算机密集型,是指I/O(硬盘/内存)的读写操作在很短时间内就可以完成,CPU需要大量的计算和处理。
CPU占用率高,全速运转,所以就需要IO的读写要快。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
IO密集型
系统运作的大部分时间是CPU在等IO的读写操作,CPU的占用率较低,导致线程空余的时间多,所以此时要在CPU等待过程中启用其他线程去继续使用CPU,提高其使用效率
线程中等待时间占比较多,就要启用较多的线程,提高CPU的使用率;反之,若CPU使用时间占比较高,就要少启用几个线程
在IO密集型中通常会使用大量的外部数据源。
总结:
CPU密集型,对硬盘等读写操作并不会很频繁,主要是CPU处理一些较为复杂的工作,占比率大
IO密集型,对硬盘等读写操作较为频繁,CPU在处理数据是常会等待IO读写操作结束,比较耗时,CPU的使用率低
多线程:
优点:相对进程更轻量级,占用资源少
缺点:相比于进程,多线程只有并发执行,只能使用一个CPU;
相比于协程,启动数目有限制,占用内存资源,有线程切换开销;
使用场景:IO密集型计算、同时运行的任务数目要求不多
使用线程池的好处:
1.提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源;
2.适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短;
3.防御功能:能有效避免系统因为创建线程过多,而导致系统符合过大相应变慢等问题;
4.代码优势:使用线程池的语法比自己新建线程执行线程更加简洁。
import threading
glock = threading.Lock()
glock.acquire() # 加锁
# 进行读写操作
glock.release() # 释放锁
加锁阻塞线程,防止其他线程同时访问同一个数据源从而出现问题,当前线程访问完毕后,释放锁,使得下一个线程正常执行
while True,循环执行当前线程。线程池中有x个线程,就执行x个任务,但每一个任务都可以循环执行多次,以此实现代码的完整性。
多进程
优点:可以利用多核CPU并行运算
缺点:占用资源最多、可启用数目比线程少
适用场景:CPU密集型计算
进程池中有x个进程,则先同时执行列表中的x个任务,等某一个任务结束后,再执行列表中的下一个任务。
多协程
优点:内存开销最少、启动协程数量最多
缺点:支持的库有限制(requests无法使用协程,如果想用协程进行爬虫可以使用aiohttp)、代码实现复杂
适用场景:IO密集型计算、需要超多任务运行、但有现成库支持的场景
总结:
当要执行的任务属于CPU密集型时,使用多进程;
当要执行的任务时IO密集型时,则考虑一下几点问题:
是否需要超多任务量,是否有协程库支持,协程实现的代码复杂度是否可以接受
如果“是”,则选择使用多协程
如果“否”,则选择多线程
对于多线程和多进程中,提到的并行和并发
并发是指一个处理器同时处理多个任务
并行是指多个处理器或者是多核处理器同时处理多个不同的任务
并发--逻辑上的同时进行,带还是会有一个先后顺序,例如一个人要在一段时间内处理n件事
并行--实际中的同时进行、同时发生,比如3个人处理3同时处理三件事