当前位置: 首页 > 后端技术 > Python

Python多任务编程

时间:2023-03-26 12:26:00 Python

文章转载自东菱阁好麦好买网前言Python程序代码加载和执行的顺序是自上而下的,但并非所有真正需要代码处理的任务都需要按部就班顺序执行,通常在为了提高代码执行的效率,需要同时执行多个代码执行任务,这就是多任务编程的要求。计算机基本模型由CPU、RAM和各种资源(键盘、硬盘、显卡、网卡等)组成。代码的执行过程实际上就是CPU、相关寄存器和RAM之间进行相关处理的过程。在单核CPU场景下,一段代码在交给CPU执行之前,会在就绪队列中,CPU会在执行时很快返回代码的结果,所以下面的代码CPU轮流执行不同的进程。它速度很快,而且在性能方面,它仍然感觉像是在同时执行。不同就绪队列之间读取和保存结果的过程称为上下文切换。由于进程之间的切换会造成一定的时间等待和资源消耗,为了减少等待时间和资源消耗,引入了线程设计。线程是当一个进程的队列被授权占用CPU时,在共享进程资源的环境下,该进程的所有线程队列按照优先级由CPU执行。无论是进程还是线程,其队列和资源切换都是由操作系统控制的。同时,线程的切换也是非常耗性能的。为了让各个线程的调度更加节省资源,协程的设计出现了。协程在进程或线程环境中执行,有自己的寄存器上下文和栈,调度完全由用户控制,相当于函数方法的调度。对于多任务编程,为了实现多任务代码的高效执行,我们需要明确以下几个概念的特点和区别,以便我们根据实际需要选择最佳的多任务编程方式。并行是指多个进程的指令同时在多个处理器上执行。并发是指同一时间只能执行一条进程指令,而多条进程指令却快速轮流执行,从而出现多个进程同时执行的宏观效果。进程进程是程序的运行状态,进程间的数据共享需要外部存储空间。线程线程是进程的一部分。一个进程可以包含一个或多个线程。同一进程中线程之间的数据共享是内部共享。协程协程是用户模式下的轻量级线程。一个进程可以包含一个或多个协程,一个线程也可以包含一个或多个协程。协程的调度完全由用户控制,同一进程中协程之间的数据共享属于内部共享。多线程处理由于Python是一种动态编译的语言,所以不同于C/C++、Java等静态语言。它在运行时逐句编译和执行。用C语言实现的Python解释器,通常称为CPython,也是Python环境的默认编译器。在Cpython解释器中,为了防止多个线程同时执行同一个Python代码段,保证线程数据安全,引入了全局解释器锁(GIL,GlobalInterpreterLock)处理机制,相当于一个相互排除锁。所以即使一个进程下开启了多线程,也只能同时执行一个线程。所以Python的多线程是伪线程,性能不高,发挥不了CPU多核的优势。另外,GIL并不是Python的特性。它是在实现Python解释器(CPython)时引入的概念。GIL在解释器级别保护数据。要保护用户自己的数据,还是需要自己上锁。默认情况下,由于GIL的存在,为了让多线程(threading)更高效,需要使用join方法来阻塞无序线程。下面的代码可以看出区别。frommultiprocessingimportProcessimportthreadingimportos,timel=[]stop=time.time()defwork():globalstoptime.sleep(2)print('===>',threading.current_thread().name)stop=time.time()deftest1():foriinrange(400):p=threading.Thread(target=work,name="test"+str(i))l.append(p)p.start()deftest2():foriinrange(400):p=threading.Thread(target=work,name="test"+str(i))l.append(p)p.start()forpinl:p.join()if__name__=='__main__':print("CPUCore:",os.cpu_count())#本机为4核print("Worker:400")#测试线数start=time.time()test1()active_count=threading.active_count()while(active_count>1):active_count=threading.active_count()continuetest1_result=stop-startstart=time.time()l=[]test2()active_count=threading。active_count()while(active_count>1):active_count=threading.active_count()继续print('Threadruntimeis%s'%(test1_result))print('Threadjoinruntimeis%s'%(stop-start))执行结果如下:Threadruntimeis4.829492807388306Threadjoinruntimeis2.053645372390747从上面的结果可以看出,在多线程中join被阻塞后,执行效率提升了很多。多进程多线程多任务编程的本质是CPU占用方式的调度处理。对于python下的多任务处理,有多种编程方式可供选择,分别介绍。Multiprocessing、多线程和异步协程(Asyncio),在实际使用中如何选择?我们先来看看下面这个程序的执行效果。从多处理导入进程从线程导入Threadimportos,timel=[]defwork():res=0foriinrange(100000000):res*=ideftest1():foriinrange(4):p=Process(target=work)l.append(p)p.start()deftest2():foriinrange(4):p=Thread(target=work)l.append(p)p.start()forpinl:p.join()if__name__=='__main__':print("CPUCore:",os.cpu_count())#本机为4核print("Worker:4")#工作线程或子线程数processesstart=time.time()test1()while(l[len(l)-1].is_alive()):continuestop=time.time()print('进程运行时间是%s'%(stop-start))start=time.time()l=[]test2()while(l[len(l)-1].is_alive()):continuestop=time.time()print('线程运行时间为%s'%(stop-start))执行结果如下:CPUCore:4Worker:4Processruntimeis11.030176877975464Threadruntimeis17.0117769241333从上面的结果可以看出,同一个函数使用Process和不同的方法Threadtoexecute时代不一样,whys有什么区别?multiprocessing方式使用子进程代替线程,有效绕过了全局解释器锁GIL(GlobalInterpreterLock),充分利用了多核CPU的性能,所以在多核CPU环境下,优于多核-threading方法的效率高。协程也称为微线程。协程也可以看作是标记函数。不同标记函数的执行和切换就是协程的切换,完全由程序员控制。协程一般使用gevent库,早期使用比较麻烦,所以在python3.7之后的版本中,对协程的使用进行了优化。执行代码如下:importasyncioimporttimeasyncdefwork(i):awaitasyncio.sleep(2)print('===>',i)asyncdefmain():start=time.time()l=[]foriinrange(400):p=asyncio.create_task(work(i))l.append(p)forpinl:awaitpstop=time.time()print('runtimeis%s'%(stop-start))asyncio.run(main())执行结果如下:运行时间为2.0228068828582764另外,在默认环境下,协程是单线程模式下执行的异步操作,无法发挥多线程的性能-处理器。为了提高执行效率,协程调用方法可以在多个进程中执行。代码示例如下:frommultiprocessingimportProcessimportasyncioimportos,timel=[]async_result=0asyncdefwork1():res=0foriinrange(100000000):res*=i#coroutineentryasyncdefasync_test():m=[]foriinrange(4):p=asyncio.create_task(work1())m.append(p)forpinm:awaitpasyncdefasync_test1():awaitasyncio.create_task(work1())defasync_run():asyncio.run(async_test1())#多进程入口deftest1():foriinrange(4):p=Process(target=async_run)l.append(p)p.start()if__name__=='__main__':print("CPUCore:",os.cpu_count())#本机为4核print("Worker:4")#Work线程或子进程数start=time.time()asyncio.run(async_test())stop=time.time()print('Asyncio运行时间是%s'%(stop-start))start=time.time()test1()while(l[len(l)-1].is_alive()):continuestop=time.time()print('ProcessAsyncioruntimeis%s'%(stop-start))执行结果如下:CPUCore:4Worker:4Asyncioruntimeis18.89663052558899ProcessAsyncioruntimeis10.865562438964844结果在多进程中调用多个协程的方法可以显着提高执行效率多任务编程的选择是否由于以上决定选择了多处理(multiprocessing)模式?我们再看下面的代码:frommultiprocessingimportProcessfromthreadingimportThreadimportos,timel=[]#Onlycalculatedefwork1():res=0foriinrange(100000000):res*=i#Onlyoutputdefwork2():time.sleep(2)print('===>')#多进程,只计算deftest1():foriinrange(4):p=Process(target=work1)l.append(p)p.start()#多进程,只输出deftest_1():foriinrange(400):p=Process(target=work2)l.append(p)p.start()#多线程,只计算deftest2():foriinrange(4):p=Thread(target=work1)l.append(p)p.start()forpinl:p.join()#多线程,只输出deftest_2():foriinrange(400):p=Thread(target=work2)l.append(p)p.start()forpinl:p.join()if__name__=='__main__':print("CPUCore:",os.cpu_count())#机器是4核start=time.time()test1()while(l[len(l)-1].is_alive()):continuestop=time.time()测试结果=stop-startstart=time.time()l=[]test_1()while(l[len(l)-1].is_alive()):continuestop=time.time()test1_result=stop-startstart=time.time()l=[]test2()while(l[len(l)-1].is_alive()):continuestop=time.time()test2_result=stop-startstart=time.time()l=[]test_2()while(l[len(l)-1].is_alive()):continuestop=time.time()test3_result=stop-startprint('进程运行时间为%s'%(test_result))print('进程I/O运行时间为%s'%(test1_result))print('线程运行时间为%s'%(test2_result))print('线程I/O运行时间为%s'%(stop-start))执行结果如下:Processruntimeis10.77662968635559ProcessI/Oruntimeis2.9869778156280518Threadruntimeis16.842355012893677ThreadI/Oruntimeis2.024587869644165对比结果可以看出多进程的效率高,在只输出操作的情况下,多线程的效率比较高,所以在实际使用中,需要进行测试和开发根据实际情况决定。一般建议如下:?多线程用于IO密集型操作,如套接字、爬虫、web?多处理用于计算密集型任务,如数据分析