1. 视频链接
视频链接:
Python 并发编程实战,用多线程、多进程、多协程加速程序运行
2 有哪些程序提速方法

多线程并发的基础在于,代码在进行IO数据交互时,系统仍旧可以分配CPU进行计算。
3.python对并发程序的支持
- 多线程:threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴等待IO完成;
- 多进程:multiprocessing,利用多核CPU的能力,真正的并行执行任务;
- 异步IO:asyncio,在单线程利用CPU和IO同时执行的原理,实现函数的异步执行。
- 其他一些函数来提供辅助:使用Lock对资源加锁,防止冲突访问(如多个程序对同一个文件进行写入操作);使用Queue实现不同线程、进程之间的数据通信,实现生产者-消费者模式;使用线程池Pool/进程池Pool,简化线程/进程的任务提交、等待结束、获取结果;使用subprocess启动外部程序的进程,并进行输入输出交互。
4.怎样选择多线程、多进程和多协程?
- 多线程:Thread
- 多进程:Process
- 多协程:Coroutine
4.1 什么是CPU密集型计算(CPU-bound)、IO密集型计算(IO-bound)?
- CPU-bound:也叫计算密集型,是指I/O在很短的事件就可以完成,CPU需要大量的计算和处理,特点是CPU占用率非常高。
- IO-bound:系统运作大部分的状况是CPU在等I/O(硬盘/内存)的读/写操作,CPU占用率仍然较低。
4.2 多线程、多进程、多协程的对比
- 多进程Process(multiprocessing)
- 多线程Thread(threading)
- 多协程Coroutine(asyncio)
在这其中,一个进程中可启动N个线程,一个线程中可以启动N个协程。
4.2.1 多进程Process
- 优点:可以利用多核CPU并行计算
- 缺点:占用资源最多、可启动数目比线程少
- 适用于:CPU密集型计算
4.2.2 多线程Thread
- 优点:相比进程,更轻量级,占用资源少。
- 缺点:相比进程,多线程只能并发执行,不能利用多CPU(GIL全局解释锁);相比协程,启动数目有限制,占用内存资源,有线程切换开销。
- 适用于:IO密集型计算、同时运行的任务数目要求不多
4.2.3 多协程Coroutine
- 优点:内存开销最少、启动协程数量最多
- 缺点:支持的库有限制(aiohttp VS requests)、代码实现复杂
- 适用于:IO密集型计算、需要超多任务运行、但有现成库支持的场景

4.3 全局解释器锁GIL
4.3.1 python速度慢的两大原因
- 动态类型语言,边解释边执行
- 由于GIL,无法利用多核CPU并发执行
4.3.2 GIL是什么
全局解释锁(Global Interpreter Lock,GIL)
是计算机程序设计语言解释器用于同部线程的一种机制,它使得任何时刻仅有一个进程在执行。即使在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。
4.3.3 GIL的作用
为了解决多线程之间数据完整性和状态同步问题。
Python中对象的管理,时使用计数器进行的,当引用数为0时,则释放对象。
若没有GIL,就会出现一个问题
当线程A中计数器减少1以后,线程被线程B拿去了,B进行了计数器减一,此时使用次数为0,释放obj。然后线程A继续判断是否释放obj。但此时obj已被释放,不存在了。这样子就会导致内存错误。
4.3.4 怎样规避GIL带来的限制
- 多线程threading机制依然是有用的,用于IO密集型计算。因为在IO期间,线程会释放GIL,实现CPU和IO的并行,因此多线程用于IO密集型计算依然可以大幅提升速度。但是多线程用于CPU密集型计算时,只会更加拖慢速度。
- 使用multiprocessing的多进程机制实现并行计算、利用多核CPU优势。为了应对GIL的问题,Python提供了multiprocessing
5. 多线程
5.1 使用多线程,加速爬虫程序
代码见码云:
咸鱼/并发编程/01.multi_thread_craw.py
咸鱼/并发编程/02.producer_consumer_spider.py (用到了queue)
两个都是使用threading.Thread进行加速。
5.2 Python线程安全问题
线程安全指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
针对会出现线程安全问题的代码,一般是用lock锁:
import threading
lock = threading.Lock()
with lock:
执行可能会出现线程安全的代码
代码:
咸鱼/并发编程/03.lock_concurrent.py
5.3 好用的线程池
5.3.1 线程池的原理
线程的生命周期
新建线程系统需要分配资源、终止线程系统需要回收资源,如果可以重用线程,则可以减去新建/终止的开销。
线程池中是预先建立好的线程,这些线程可以被重复使用。
5.3.2 线程池的好处
(1)提升性能:因为减去了大量的新建、终止线程开销,重用了县城资源;
(2)使用场景:适合处理突发性大量请求或需要大量线程完成任务,但实际处理时间较短;
(3)防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大响应变慢等问题;
(4)代码优势:使用线程池的语法比自己新建线程执行线程更加简洁。
5.3.3 ThreadPoolExecutor的使用语法

代码:咸鱼/并发编程/04.thread_pool.py
6.线程池在web服务中实现加速
6.1 web服务的架构以及特点

web后台服务的特点:
- web服务对相应时间要求非常高,比如要求200MS返回;
- web服务有大量的依赖IO操作的调用,比如磁盘文件、数据库、远程API
- web服务经常需要处理几万人、几百万人的同时请求。
6.2 使用线程池ThreadPoolExecutord加速
使用ThreadPoolExecutord的好处:
- 方便的将磁盘文件、数据库、远程API的IO调用并发执行;
- 线程池的线程数目不会无限创建(导致系统挂掉),具有防御功能。
代码:咸鱼/并发编程/05.flask_thread_pool.py
7.多进程
用多进程在多CPU上并行执行
7.1 多进程multiprocessing的优势
多进程thread只针对IO操作,如果遇到CPU密集型计算,多线程反而会降低执行速度。
虽然有全局解释器锁GIL,但是因为进行IO时,仍会进行cpu计算,所以多线程依然可以加速运行。
但出现CPU密集型计算时,线程之间的自动切换,会导致切换耗时变多,多线程甚至减慢了运行速度。(4.2中多线程的缺点)
7.2 多进程multiprocessing知识梳理(对比多线程threading)

注意:(1)池化技术与直接使用多线程、多进程所引用的库不同;(2)直接使用target、args可以不用,但池化时,一定不能用。(注意可以不用与一定不能用的区别)
代码:咸鱼/并发编程/06.thread_process_cpu_bound .py
7.3 在flask中使用多进程池加速
代码:咸鱼/并发编程/07.flask_process_pool .py
注意:
- 多进程之间是相互隔离的,所以pool一定要放在所有的声明函数下面;
- 一定要定义到main函数里面。
8.协程
在单线程内实现并发
协程核心原理:用一个超级循环(其实就是while true)循环,配合IO多路复用原理(IO时CPU可以干其他事情)
代码:咸鱼/并发编程/08.async_spider .py
8.1 在异步IO中使用信号量控制爬虫并发度
信号量(Semaphore):
信号量又称为旗语,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。
- 当线程完成一次对该semaphore对象的等待时(wait)时,该计数值减一;
- 当线程完成一次对semaphore对象的释放(release)时,计数值加一;
- 当即数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象 变成signaled状态;
- semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。
结尾
如果有文字错误、描述错误等,希望各位能私聊或留言指出。
该内容纯属个人学习笔记,如冒犯学习视频作者权益,请及时联系,以免造成非必要纠纷。