Python 并發編程從入門到進階
Python多線程
Python有真正的多線程嗎?我本來以為是沒有的。因為CPython解釋器本身就不是線程安全的,因此有全局解釋器鎖(GIL),一次只允許使用一個線程執行Python字節碼。換句話說,Python即便是有多線程,也會受GIL限制,按順序執行。所以我就以為Python是沒有多線程的,也就是一個Python進程不能同時使用多個CPU核心。然而,Python標準庫中所有執行阻塞型I/O操作的函數,在等待操作系統返回結果時,都會釋放GIL,允許其他線程運行。這就意味著Python線程在I/O密集型應用中還是可以發揮作用的,比如一個Python線程等待網絡響應時,阻塞型I/O函數會釋放GIL,再運行一個線程。再比如time.sleep()函數也會釋放GIL。
Python多進程
但是對于CPU密集型應用來說,要想利用所有可用的CPU核心,就得使用多進程,規避GIL。
多線程與多進程對比
什么時候用多線程?什么時候用多進程?
I/O密集型應用使用多線程,CPU密集型應用使用多進程。
什么是I/O密集型應用?什么是CPU密集型應用?
簡單來說,I/O密集一般涉及到網絡、磁盤IO。而CPU密集指的是計算比較多。
創建多線程可以使用concurrent.futures,創建多進程可以使用multiprocessing。
多線程與協程對比
多線程存在著切換開銷,同時為了避免寫變量沖突,在控制共享資源時需要加鎖,因此編寫程序會比較復雜比較困難。而協程是通過單線程實現的并發,既能自由切換,也不需要鎖機制,執行效率要高很多。
多線程和協程有個共同點是只對I/O密集型應用有效,因為GIL限制。如果想處理CPU密集型應用,那么可以結合多進程一起使用,以提高CPU使用率。
asyncio包
asyncio包比較多用來實現Python協程并發,原書在這一章節引用了很多示例,穿插了很多代碼,導致我看起來有點亂,不是很清楚到底該怎么使用這個包。所以我看了一下官方文檔:
https://docs.python.org/3/library/asyncio.html
從Python3.5開始引入了async和await,替代了@asyncio.coroutine和yield from語法,語義更簡潔更明確了。并且從Python3.7開始引入了asyncio.run(),替代了這一串代碼:
- loop = asyncio.get_event_loop()
- try:
- loop.run_until_complete(main())
- finally:
- loop.close()
創建task的語法也發生了變化,可以用asyncio.create_task:
- async def coro():
- ...
- # In Python 3.7+
- task = asyncio.create_task(coro())
- ...
- # This works in all Python versions but is less readable
- task = asyncio.ensure_future(coro())
- ...
對于多個并行task可以用asyncio.gather,替代asyncio.wait:
- task_list = []
- for i in range(5):
- task = asyncio.create_task(async_func(i))
- task_list.append(task)
- done, pending = await asyncio.wait(task_list, timeout=None)
- for done_task in done:
- print((f"[{current_time()}] 得到執行結果 {done_task.result()}"))
- task_list = []
- for i in range(5):
- task = asyncio.create_task(func(i))
- task_list.append(task)
- results = await asyncio.gather(*task_list)
- for result in results:
- print((f"[{current_time()}] 得到執行結果 {result}"))
所以對于第三方包的學習,最好是看看最新的官方文檔,說不定已經進行了很多優化。
最后,完結,撒花。