Python多线程


CPU密集型计算

CPU密集型(CPU—bound)

CPU密集型也叫做计算密集型,是指I/O在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率高

例如:压缩解压缩、加密解密、正则表达式搜索

IO密集型计算

IO密集型指的是系统运作大部分的状况是CPU在等I/O(硬盘/内存)的读/写操作,CPU占用率低

例如:文件处理程序、网络爬虫程序、读写数据库程序

多进程、多线程、多协程的对比

一个进程中可以启动N个线程

一个线程中可以启动N个协程

多进程 Process (multiprocessing)

  • 优点:可以利用多核CPU并行运算
  • 缺点:占用资源最多、可启动数目比线程少
  • 适用于:CPU密集型计算

多线程 Thread (threading)

  • 优点:相比进程,更轻量级、占用资源少
  • 缺点:
  • 相比进程:多线程只能并发执行,不能利用多CPU(GIL)
  • 相比协程:启动数目有线程,占用内存资源,有线程切换开销
  • 适用于:IO密集型计算、同时运行的任务数目要求不多

多协程 Coroutine (asyncio)

  • 优点:内存开销最少、启动协程数量最多
  • 缺点:支持的库有限制(aiohttp vs requests)、代码实现复杂
  • 适用于:IO密集型计算、需要超多任务运行、但有线程库支持的场景

GIL

GIL的全称是:Global Interpreter Lock,意思就是全局解释器锁

python多cpu计算 python多核cpu_多线程

 

即使电脑有多核CPU,单个时刻只能使用一个。

多线程threading机制依然是游泳的,用于IO密集型计算

因为在I/O期间,线程会释放GIL,实现CPU核IO的并行

但是多线程用于CPU密集型计算时,只会更加拖慢速度

使用multiprocessing的多线程机制实现并行计算、利用多核CPU优势为了应对GIL的问题,Python提供了multiprocessing

创建多线程的方法

单线程是通过顺序的。

多线程是无序的顺序。

多组件的Pipeline技术架构

复杂的事情一般都不会一下子做好,会分成多个步骤一个个完成

python多cpu计算 python多核cpu_线程安全_02

生产者消费者爬虫的架构 

 

python多cpu计算 python多核cpu_线程安全_03

多线程数据通信的queue.Queue

用于多线程之间的,线程安全的数据通信


1、导入类库 import queue 2、创建Queue q = queue.Queue() 3、添加元素 q.put(item) 4、获取元素 item = q.get() 5、查询状态 # 查看元素的多少 q.qsize() # 判断是否为空 q.empty() # 判断是否已满 q.full()


线程安全概念

线程安全指某个函数、函数库再多线程环境中被调用时,能够正确的处理多个线程之间的共享变量,使程序功能正确完成。

由于线程的执行随时会发生切换,就造成了不可预料的结果,出现线程不安全

用lock解决线程安全问题

用try-finally模式


import threading lock = threading.Lock() lock.acquire() try:   ... finally:    lock.release()


用with模式


import threading lock = threading.Lock() with lock:   ...


线程池的原理

python多cpu计算 python多核cpu_多进程_04

 

新建线程系统需要分配资源、终止线程系统需要回收资源

如果可以重用线程,则可以减去新建/终止的开销

python多cpu计算 python多核cpu_多线程_05

 

优点:

1、提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源;

2、适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短

3、防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题

4、代码优势:使用线程池的语法比自己新建线程执行线程更加简洁

ThreadPoolExecutor的使用语法


from concurrent.futures import ThreadPoolExecutor,as_completed
 
with ThreadPoolExecutor() as pool:
    results = pool.map(craw, urls)
    for result in results:
        print(result)
# 用法:map函数,很简单,注意map的结果和入参是顺序对应的
 
with Thread PoolExecutro() as pool:
    futures = [ pool.submit(craw, url)
                    for url in urls ]
    for future in futures:
        print(future.result())
    for future as_completed(futures):
        print(future.result())
# 用法: future模式,更强大,注意使用as_completed顺序是不定的


使用线程池ThreadPoolExecutor加速

优点:

1、方便的将磁盘文件、数据库、远程API的IO调用并发执行

2、线程池的线程数目不会无线创建(导致系统挂掉),具有防御功能

多进程multiprocessing

有了多线程threading,为什么还要用多进程multiprocessing

虽然有GIL但多线程依然可以加速运行

CPU密集型计算

线程的自动切换反而变成了负担

多线程甚至减慢了运行速度

multiprocessing模块就是python为了解决GIL缺陷引入的一个模块,原理是用多进程在多CPU并行执行

多进程和多线程的使用方式几乎完全一样

python多cpu计算 python多核cpu_线程安全_06

 

python多cpu计算 python多核cpu_多线程_07

Python异步IO库介绍:asyncio


import asyncio
# 获取时间循环
loop = asyncio.get_event_loop()

# 定义协程
async def myfunc(url):
    await get_url(url)

# 创建task列表
tasts = [loop.create_task(myfunc(url))for url in urls]

# 执行爬虫事件列表
loop.run_until_complete(asyncio.wait(tasks))


注意:

要用在异步IO编程中

依赖的库必须支持异步IO特性

爬虫引用中:

requests不支持异步

需要用aiohttp

信号量(英语:Semaphore)

信号量(英语:Semaphore)又称为信号量、旗语

是一个同步对象,用于保持在0至指定最大值之间的一个计数值。

  • 当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;·当线程完成一次对semaphore对象的释放(release)时,计数值减一。
  • 当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;·当线程完成一次对semaphore对象的释放(release)时,计数值加一。
  • 当计数值为O,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态. semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。