文章转载自东凌阁
GoodMai 好买网

前言
Python程序代码都是按自上而下的程序加载并执行的,但理论须要代码解决的工作并不都是须要循序渐进的程序执行,通常为进步代码执行的效率,须要多个代码执行工作同时执行,也就是多任务编程的需要。
根本的计算机模型是由CPU、RAM及各种资源(键盘、硬盘、显卡、网卡等)组成,代码的执行过程,理论就是CPU和相干寄存器及RAM之间的相干解决的过程。在单核CPU场景下,一段代码交由CPU执行前,都会处于就绪队列中,CPU执行时很快就会返回该段代码的后果,所以不同过程的代码是轮流由CPU执行的,因为CPU执行速度很快,在体现上仍会被感觉是同时执行的。不同就绪队列间的读入与后果保留被称之为上下文切换,因为过程间切换会产生肯定的工夫期待及资源的耗费,所以为了缩小等待时间和资源的耗费,就引入了线程的设计。线程是当过程的队列被受权占用CPU时,该过程的所有线程队列在共享该过程资源的环境下按优先级由CPU执行。无论是过程还是线程,其队列及资源切换都是由操作系统进行管制的,同时线程的切换也是十分耗费性能的,为了使各线程的调度更节约资源,就呈现了协程的设计。协程是在过程或线程环境下执行的,其领有本人的寄存器上下文和栈,调度是齐全由用户管制的,相当于函数办法的调度。对于多任务编程,若要实现代码的多任务高效率执行,咱们要清晰如下这几个概念的特点及其区别,能力依据理论需要,选用最佳的多任务编程办法。

并行
指在同一时刻有多个过程的指令在多个处理器上同时执行。
并发
是指在同一时刻只能有一个过程的指令执行,但多个过程指令被疾速轮换执行,使得在宏观上具备多个过程同时执行的成果。
过程
过程是程序的运行态,过程间数据共享须要借助内部存储空间。
线程
线程是过程的组成部分,一个过程能够蕴含一个或多个线程,同一过程内线程间数据共享属于外部共享。
协程
协程是一种用户态的轻量级线程,一个过程能够蕴含一个或多个协程,也能够在一个线程蕴含一个或多个协程。协程的调度齐全由用户管制,同一过程内协程间数据共享属于外部共享。
多线程解决
因为Python是动静编译的语言,与C/C++、Java等动态语言不同,它是在运行时一句一句代码地边编译边执行的。用C语言实现的Python解释器,通常称为CPython,也是Python环境默认的编译器。在Cpython解释器中,为避免多个线程同时执行同一 Python 的代码段,确保线程数据安全,引入了全局解释器锁(GIL, Global Interpreter Lock)的解决机制, 该机制相当于一个互斥锁,所以即使一个过程下开启了多线程,但同一时刻只能有一个线程被执行。所以Python 的多线程是伪线程,性能并不高,也无奈利用CPU多核的劣势。

另,GIL并不是Python的个性,他是在实现Python解释器(Cpython)时所引入的一个概念,GIL爱护的是解释器级的数据,爱护用户本人的数据仍须要本人加锁解决。在默认状况下,因为GIL的存在,为了使多线程(threading)执行效率更高,须要应用join办法对无序的线程进行阻塞,如下代码能够看到区别。

from multiprocessing import Processimport threadingimport os,timel=[]stop=time.time()def work():    global stop    time.sleep(2)    print('===>',threading.current_thread().name)    stop=time.time()def test1():    for i in range(400):        p=threading.Thread(target=work,name="test"+str(i))         l.append(p)        p.start()def test2():    for i in range(400):        p=threading.Thread(target=work,name="test"+str(i))         l.append(p)        p.start()    for p in l:        p.join()if __name__ == '__main__':    print("CPU Core:",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()        continue    test1_result=stop-start    start=time.time()    l=[]    test2()    active_count=threading.active_count()    while (active_count>1):        active_count=threading.active_count()        continue        print('Thread run time is %s' %(test1_result))print('Thread join run time is %s' %(stop-start))

执行后果如下:

Thread run time is 4.829492807388306 Thread join run time is 2.053645372390747

由上后果能够看到, 多线程时join阻塞后执行效率进步了很多。
多过程与多线程
多任务编程的实质是CPU占用办法的调度解决,对于python下多任务处理有多种编程办法可供选择,别离有多过程(multiprocessing)、多线程(threading)及异步协程(Asyncio),在理论应用中该如何抉择呢?咱们先看如下一段程序的执行成果。

from multiprocessing import Processfrom threading import Threadimport os,timel=[]def work():    res=0    for i in range(100000000):        res*=idef test1():    for i in range(4):        p=Process(target=work)         l.append(p)        p.start()def test2():    for i in range(4):        p=Thread(target=work)         l.append(p)        p.start()    for p in l:        p.join()if __name__ == '__main__':    print("CPU Core:",os.cpu_count()) #本机为4核    print("Worker: 4") #工作线程或子过程数    start=time.time()    test1()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    print('Process run time is %s' %(stop-start))        start=time.time()    l=[]    test2()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    print('Thread run time is %s' %(stop-start))

执行后果如下:

CPU Core: 4 Worker: 4 Process run time is 11.030176877975464 Thread run time is 17.0117769241333

从下面的后果,咱们能够看到同一个函数用Process及Thread 不同的办法,执行的工夫是不同的,为什么会产生这样的差别?
多过程(multiprocessing)办法应用子过程而非线程,其无效地绕过了全局解释器锁GIL(Global Interpreter Lock), 并充分利用了多核CPU的性能,所以在多核CPU环境下,其比多线程形式效率要高。

协程
又称为微线程,协程也可被看作是被标注的函数,不同被表注函数的执行和切换就是协程的切换,其齐全由编程者自行管制。协程个别是应用 gevent库,在晚期这个库用起来比拟麻烦,所以在python 3.7当前的版本,对协程的应用办法做了优化。执行代码如下:

import asyncioimport timeasync def work(i):    await asyncio.sleep(2)    print('===>',i)async def main():    start=time.time()    l=[]    for i in range(400):        p=asyncio.create_task(work(i))        l.append(p)    for p in l:        await p    stop=time.time()    print('run time is %s' %(stop-start))asyncio.run(main())

执行后果如下:
run time is 2.0228068828582764
另,默认环境下,协程是在单线程模式下执行的异步操作,其并不能施展多处理器的性能。为了晋升执行效率,能够在多过程中执行协程调用办法,代码用例如下:

from multiprocessing import Processimport asyncioimport os,timel=[]async_result=0async def work1():    res=0    for i in range(100000000):        res*=i# 协程入口async def async_test():    m=[]    for i in range(4):        p=asyncio.create_task(work1())        m.append(p)    for p in m:        await pasync def async_test1():    await asyncio.create_task(work1())def async_run():    asyncio.run(async_test1())# 多过程入口def test1():    for i in range(4):        p=Process(target=async_run)         l.append(p)        p.start()if __name__ == '__main__':    print("CPU Core:",os.cpu_count()) #本机为4核    print("Worker: 4") #工作线程或子过程数    start=time.time()    asyncio.run(async_test())    stop=time.time()        print('Asyncio run time is %s' %(stop-start))    start=time.time()    test1()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    print('Process Asyncio run time is %s' %(stop-start))

执行后果如下:

CPU Core: 4 Worker: 4 Asyncio run time is 18.89663052558899 Process Asyncio run time is 10.865562438964844

如上后果,在多过程中调用多协程的办法,执行效率明显提高。

多任务编程抉择
如上所后果是否是就决定肯定要抉择多过程(multiprocessing)模式呢?咱们再看下如下代码:

from multiprocessing import Processfrom threading import Threadimport os,timel=[]# 仅计算def work1():    res=0    for i in range(100000000):        res*=i# 仅输入def work2():    time.sleep(2)    print('===>')# 多过程,仅计算def test1():    for i in range(4):        p=Process(target=work1)         l.append(p)        p.start()# 多过程,仅输入def test_1():    for i in range(400):        p=Process(target=work2)         l.append(p)        p.start()# 多线程,仅计算def test2():    for i in range(4):        p=Thread(target=work1)         l.append(p)        p.start()    for p in l:        p.join()# 多线程,仅输入def test_2():    for i in range(400):        p=Thread(target=work2)         l.append(p)        p.start()    for p in l:        p.join()if __name__ == '__main__':    print("CPU Core:",os.cpu_count()) #本机为4核    start=time.time()    test1()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    test_result=stop-start        start=time.time()    l=[]    test_1()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    test1_result=stop-start    start=time.time()    l=[]    test2()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    test2_result=stop-start    start=time.time()    l=[]    test_2()    while (l[len(l)-1].is_alive()):        continue    stop=time.time()    test3_result=stop-start    print('Process run time is %s' %(test_result))    print('Process I/O run time is %s' %(test1_result))    print('Thread run time is %s' %(test2_result))    print('Thread I/O run time is %s' %(stop-start))

执行后果如下:

Process run time is 10.77662968635559 Process I/O run time is 2.9869778156280518 Thread run time is 16.842355012893677 Thread I/O run time is 2.024587869644165

由后果可看,在仅计算的操作时,多过程效率比拟高,在仅输入的操作时,多线程的效率比拟高,所以在理论应用中要依据理论状况测试决定。通用的倡议如下:
多线程(threading)用于IO密集型,如socket,爬虫,web
多过程(multiprocessing)用于计算密集型,如数据分析