Python多线程、多进程编程
1 简介
参考:https://www.bilibili.com/video/BV1bK411A7tV?spm_id_from=333.999.0.0
python线程池ThreadPoolExecutor与进程池ProcessPoolExecutor - HarvardFly - 博客园 (cnblogs.com)
使用的库:concurrent.futures
官方介绍:
The concurrent.futures module provides a high-level interface for asynchronously executing callables.
The asynchronous execution can be performed with threads, using ThreadPoolExecutor, or separate processes, using ProcessPoolExecutor. Both implement the same interface, which is defined by the abstract Executor class.
1.1 基本知识
多线程:threading,只能利用单核CPU(由于GIL)
多进程:multiprocessing,利用多核CPU
异步IO:asyncio,在单线程中利用CPU和IO同时执行的原理,实现函数异步执行。
使用Lock对资源加锁,防止冲突访问。
使用Queue实现不同线程/进程之间的数据通信。
使用线程池Pool/进程池Pool,简化线程/进程任务提交、等待结果、获取结果。
使用subprocess启动外部程序的进程,并进程输入输出交互。
1.1.1 CPU密集型(CPU-Bound)和IO密集型(IO-Bound)
CPU密集型也叫计算密集型,是指I/O在很短的时间就可以完成,CPU需要大量的计算和处理,特点是CPU占用率很高。例如:压缩解压缩、加密解密、正则表达式搜索。
IO密集型指的是系统运行大部分状况是在等I/O(磁盘/内存)的读写操作,CPU占用率很低。例如:文件处理程序、网络爬虫程序、读写数据程序。
1.1.2 多线程、多进程、多协程的对比
多进程(multiprocessing):
优点:可以利用多核CPU运行
缺点:占用资源多,可启动数目比线程少。
适用于:CPU密集型计算
多线程(threading):
优点:相比进程,更轻量级、占用资源少
缺点:相比进程,多先册亨共你只能并发执行,不能利用多CPU(GIL);相比协程,启动数目有限制,占用内存资源,有线程切换开销。
适用于:I/O密集型计算,同时运行的任务数要求不多。
多协程(asyncio):
优点:内存开销最少,启动协程数量最多。
缺点:支持的库有限制,代码实现复杂。
适用于:IO密集型计算,需要超多任务运行,但有现成库支持的场景。
一个进程中可以启动多个线程,一个线程中可以启动多个协程。
1.1.3 GIL(全局解释器锁,global interpreter lock)
GIL是计算机设计时用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行,即便在多核处理器上,使用GIL的解释器也只允许同一时间执行一个线程。
1.2 python多线程使用
使用threading库:
创建线程使用:threading.Thread,比如t = threading.Thread(target=func, args=())
启动线程使用:t.start()
等待结束使用:t.join()
多线程间通信:
使用queue.Queue,可以用于多线程间的线程安全的数据通信。
导入类库:import queue
创建Queue:q = queue.Queue()
添加元素:q.put(item),阻塞访问
获取元素:item = q.get(),阻塞访问
查看元素的个数:q.qsize()
判断是否为空:q.empty()
判断是否已满:q.full()
线程安全、Lock模块:
多线程对共享资源的访问(比如全局变量)需要互斥访问。
使用方式一:
import threading
lock = threading.Lock()
lock.acquire()
try:
# do something
finally:
lock.release()
使用方式二:
lock = threading.Lock()
with lock:
# do something
线程池:
新建线程需要分配资源、终止线程需要回收资源,使用线程池可以减去新建/终止的开销。
线程一个任务队列以及一个可重用的线程就实现了一个线程池。
线程池的好处:
提升性能:减去了大量创建、终止线程的开销,重用了线程资源。
适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短。
防御功能:能有效避免系统因为创建线程过多,从而导致系统负荷过大相应变慢的问题。
代码优势:使用线程池的语法比自己创建线程更加简洁。
进程池:
多进程,系统中运行了多个python 解释器, 他们真正的只并行计算, 但是也相应的会有一些负担。
对于CPU密集型操作,使用进程池效率比线程池更高。
2 Executor Objects
线程池和进程池都是通过抽象类Executor定义的,其说明如下:
class concurrent.futures.Executor
# An abstract class that provides methods to execute calls asynchronously. It should not be used directly, but through its concrete subclasses.
submit(fn, *args, **kwargs)
map(func, *iterables, timeout=None, chunksize=1)
shutdown(wait=True)
2.1 ThreadPoolExecutor
ThreadPoolExecutor是Executor类的一个子类。
2.1.1 submit函数
通过submit函数提交执行的函数到线程池中,done()判断线程执行的状态:
import time
from concurrent.futures import ThreadPoolExecutor
def get_thread_time(times):
time.sleep(times)
return times
# 创建线程池 指定最大容纳数量为4
executor = ThreadPoolExecutor(max_workers=4)
# 通过submit提交执行的函数到线程池中,分别sleep 1s 2s 3s 4s
task1 = executor.submit(get_thread_time, (1))
task2 = executor.submit(get_thread_time, (2))
task3 = executor.submit(get_thread_time, (3))
task4 = executor.submit(get_thread_time, (4))
print("task1:{} ".format(task1.done()))
print("task2:{}".format(task2.done()))
print("task3:{} ".format(task3.done()))
print("task4:{}".format(task4.done()))
time.sleep(2.5)
print('after 2.5s {}'.format('-'*20))
done_map = {
"task1":task1.done(),
"task2":task2.done(),
"task3":task3.done(),
"task4":task4.done()
}
# 2.5秒之后,线程的执行状态
for task_name, done in done_map.items():
if done:
print("{}:completed".format(task_name))
执行结果:
task1:False
task2:False
task3:False
task4:False
after 2.5s --------------------
task1:completed
task2:completed
2.1.2 wait函数
通过wait()判断线程执行的状态:
# wait接受3个参数,fs表示执行的task序列;
# timeout表示等待的最长时间,超过这个时间即使线程未执行完成也将返回;
# return_when表示wait返回结果的条件,默认为ALL_COMPLETED全部执行完成再返回:
wait(fs, timeout=None, return_when=ALL_COMPLETED)
# 通过wait()判断线程执行的状态
def get_thread_time(times):
time.sleep(times)
return times
start = time.time()
executor = ThreadPoolExecutor(max_workers=4)
task_list = [executor.submit(get_thread_time, times) for times in [1, 2, 3, 4]]
i = 1
for task in task_list:
print("task{}:{}".format(i, task))
i += 1
print(wait(task_list, timeout=2.5))
执行结果:
task1:<Future at 0x2c85919c5f8 state=running>
task2:<Future at 0x2c859278da0 state=running>
task3:<Future at 0x2c85927d2e8 state=running>
task4:<Future at 0x2c85927d4a8 state=running>
DoneAndNotDoneFutures(done={<Future at 0x2c859278da0 state=finished returned int>, <Future at 0x2c85919c5f8 state=finished returned int>}, not_done={<Future at 0x2c85927d4a8 state=running>, <Future at 0x2c85927d2e8 state=running>})
2.1.3 map函数
通过map返回线程的执行结果,map的返回是有序的,它会根据第二个参数的顺序返回执行的结果:
# 第一个参数fn是线程执行的函数;
# 第二个参数接受一个可迭代对象;
# 第三个参数timeout跟wait()的timeout一样,但由于map是返回线程执行的结果,如果timeout小于线程执行时间会抛异常TimeoutError。
map(fn, *iterables, timeout=None)
def get_thread_time(times):
time.sleep(times)
return times
executor = ThreadPoolExecutor(max_workers=4)
i = 1
for result in executor.map(get_thread_time,[2,3,1,4]):
print("task{}:{}".format(i, result))
i += 1
执行结果:
task1:2
task2:3
task3:1
task4:4
2.1.4 as_completed函数
as_completed返回的顺序是线程执行结束的顺序,最先执行结束的线程最早返回。
# 第一个是执行的线程列表;
# 第二个参数timeout与map的timeout一样,当timeout小于线程执行时间会抛异常TimeoutError。
as_completed(fs, timeout=None)
def get_thread_time(times):
time.sleep(times)
return times
start = time.time()
executor = ThreadPoolExecutor(max_workers=4)
task_list = [executor.submit(get_thread_time, times) for times in [2, 3, 1, 4]]
task_to_time = OrderedDict(zip(["task1", "task2", "task3", "task4"],[2, 3, 1, 4]))
task_map = OrderedDict(zip(task_list, ["task1", "task2", "task3", "task4"]))
for result in as_completed(task_list):
task_name = task_map.get(result)
print("{}:{}".format(task_name,task_to_time.get(task_name)))
执行结果:
task3:1
task1:2
task2:3
task4:4
2.2 ProcessPoolExecutor
ProcessPoolExecutor是Executor类的一个子类。
对于频繁的cpu操作,由于GIL锁的原因,多个线程只能用一个cpu,这时多进程的执行效率要比多线程高。
import math
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
PRIMES = [112272535095293] * 20
def is_prime(n):
if n == 1:
return False
if n == 2:
return True
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % 2 == 0:
return False
return True
def single_thread():
for number in PRIMES:
is_prime(number)
def multi_thread():
with ThreadPoolExecutor() as pool:
pool.map(is_prime, PRIMES)
def multi_processor():
with ProcessPoolExecutor() as pool:
pool.map(is_prime, PRIMES)
if __name__ == '__main__':
start = time.time()
single_thread()
end = time.time()
print("single thread cost: ", end-start, "seconds")
start = time.time()
multi_thread()
end = time.time()
print("multi thread cost: ", end-start, "seconds")
start = time.time()
multi_processor()
end = time.time()
print("multi processor cost: ", end-start, "seconds")
########## 测试结果 ##########
# single thread cost: 11.16333556175232 seconds
# multi thread cost: 10.30142068862915 seconds
# multi processor cost: 3.722104549407959 seconds

浙公网安备 33010602011771号