代码环境:python3.6至此,我们介绍的多任务编程方式包括:多进程:点击阅读多线程:点击阅读协程:点击阅读上一篇本文介绍的协程是个好东西。理论上所有多线程IO密集型应用都可以用协程代替,而且协程在数据使用上更安全,占用资源更少,所以协程完全没问题。替代多线程?不幸的是没有。最紧迫的问题是,截至2020年,并非所有Python库都提供异步方法。因此,在实际工作中,这些方法根据实际情况结合使用:对于CPU密集型应用,使用多进程;对于CPU密集型应用,使用多进程;对于IO密集型应用,优先考虑协程;当涉及到尚未提供异步方法的库时,请使用多线程。从python3.2版本开始,我们可以使用concurrent.futures模块进行多任务编程。该模块是对我们之前使用的多进程、多线程、协程相关的常用模块进一步抽象出来的。目的是为了方便我们使用python来处理并发,这也是现代python多任务的常用写法。在实际工作中,处理多任务的简单方法是:启动程序后,分别创建进程池、线程池、EventLoop;EventLoop负责调度所有协程(避免协程内阻塞);密集型的丢进线程池,CPU密集型的丢进进程池。这样,代码逻辑简单,又能尽可能发挥机器性能。一个简单的完整示例:frommultiprocessingimportManager,Queuefromconcurrent.futuresimportProcessPoolExecutor,ThreadPoolExecutorfromasyncioimportget_event_loop,sleepasasleep,gatherfromtimeimportsleep,timefromfunctoolsimportwrapsN_PROCESS=4N_THREADS=10thread_executor(max_workerPocutorsProcutorExecutor=N_THREADS)(max_workers=N_PROCESS)aioloop=get_event_loop()deftimer(func):@wraps(func)defwrapper(*args,**kw):print(f"{func.__name__}running...")start_at=time()try:returnfunc(*args,**kw)最后:print(f"{func.__name__}end,cost{time()-start_at:.2f}s")returnwrapperdefasync_timer(func):@wraps(func)asyncdefwrapper(*args,**kw):print(f"{func.__name__}running...")start_at=time()try:returnawaitfunc(*args,**kw)最后:print(f"{func.__name__}结束,花费{time()-start_at:.2f}s")returnwrapper@timerdefio_blocking_task():"""I/O类型阻塞调用"""sleep(1)@timerdefcpu_blocking_task():"""CPU类型阻塞调用"""for_inrange(1<<26):pass@async_timerasyncdefcoroutine_task():"""异步协程调用"""awaitsleep(1)@async_timerasyncdefcoroutine_error():"""将抛出异常的协程Call"""raiseAttributeError("ThisisanException")@async_timerasyncdefcoroutine_main():"""一般我们会写一个协程main函数,负责管理协程"""r=awaitgather(coroutine_task(),coroutine_error(),aioloop.run_in_executor(thread_executor,io_blocking_task),aioloop.run_in_executor(process_executor,cpu_blocking_task),return_exceptions=True,)打印(f"coroutine_maingot{r}")@timerdefmain():aioloop.run_ple_unte())if__name__=="__main__":main()在进行多任务编程时,要注意系统资源的控制,一般来说,我们主要专注于缓冲区和并发。Buffer一般由Queue的大小控制,并发量是同时执行的进程或线程的数量。buffer过高会增加内存消耗,并发过高会增加CPU切换开销,需要根据实际情况进行控制。
