Python并发与并行编程
概念解释
并发
并发(concurrency)指计算机似乎同时在做很多事情。例如对于单CPU机器,操作系统会在各个程序之间进行快速的切换,达到多个程序同时执行的假象。
并行
并行(Parallel)系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。
区别
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的、
只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
由于真实世界CPU数量是有限的,但是任务是无限的,因此我们一般说高并发,而不说高并行。
并发编程
在python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁),这就导致了虽然是多线程的,但是一个时间只有一个线程在占用解释器。
因此Python中的多线程是真实的并发,即使拥有多个CPU也没法充分利用。因此仅仅适用于IO密集型任务,对于CPU密集型不适用。
并行编程
对于CPU密集型工作,可以通过concurrent.futures
库提供的ProcessPoolExecutor
类来实现。
一个ProcessPoolExecutor
创建N个独立的Python解释器, N是系统上面可用CPU的个数。你可以通过提供可选参数给ProcessPoolExecutor(N)
来修改处理器数量。
编程实现
import time
import logging
import concurrent.futures
from multiprocessing import cpu_count
logging.basicConfig(level=logging.WARNING,
format="%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s")
logger = logging.getLogger("local")
class Executor(object):
def __init__(self, worker_num):
raise NotImplementedError()
def execute(self, func, batch_args):
raise NotImplementedError()
def _get_sorted_ret(self, ret_list):
sorted_ret_list = [ret[1] for ret in sorted(ret_list)]
return sorted_ret_list
@classmethod
def create(cls, config):
worker_num = config.get("worker_num")
return cls(worker_num)
class ParallelProcessExecutor(Executor):
"""多进程并行, 计算密集适用"""
def __init__(self, worker_num=None):
self.proc_num = cpu_count() if worker_num is None else min(worker_num, cpu_count())
self.process_pool = concurrent.futures.ProcessPoolExecutor(max_workers=cpu_count())
logger.info("Initialize ParallelProcessExecutor complete, proc_num={}.".format(self.proc_num))
def execute(self, f, args):
futures = [self.process_pool.submit(f, arg) for arg in args]
ret_list = [future.result() for future in concurrent.futures.as_completed(futures)]
return ret_list
class ParallelThreadExecutor(Executor):
"""多线程并发, IO密集适用"""
def __init__(self, worker_num=None):
self.worker_num = worker_num or cpu_count() * 5 # 手动设置, 或者按照CPU*5来设置
self.thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=self.worker_num)
logger.info("Initialize ParallelThreadExecutor complete, worker_num={}.".format(self.worker_num))
def execute(self, f, args):
futures = [self.thread_pool.submit(f, arg) for arg in args]
ret_list = [future.result() for future in concurrent.futures.as_completed(futures)]
return ret_list
if __name__ == "__main__":
def func(x):
time.sleep(20)
return sum(x)
args = [[1,2,3], [4,5,6]]
executor = ParallelThreadExecutor()
res = executor.execute(func, args)
print(res)
executor = ParallelProcessExecutor()
res = executor.execute(func, args)
print(res)