python 协程、进程、线程_Python 中的进程、线程、协程
1. 進程
進程是正在運行的程序實例,是內核分配資源的最基本的單元。進程擁有自己獨立的堆和棧,獨立的地址空間,資源句柄。進程由 OS 調度,調度開銷較大,在并發的切換過程效率較低。
Python 提供了一個跨平臺的多進程模塊 multiprocessing,模塊中使用 Process 類來代表一個進程對象。
1.1 多進程示例
import os
from multiprocessing import Process
# 子進程執行的代碼
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',)) # target 指定要執行的函數,args 指定參數
print('Child process will start.')
p.start() #啟動 Process 實例
p.join() #等待子進程結束后,繼續往下執行
print('Child process end.')
Parent process 274.
Child process will start.
Run child process test (298)...
Child process end.
1.2 進程池示例
import os, time
from multiprocessing import Pool
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(2) # 創建對象池,并設置進程池大小,默認大小是 CPU 核數
for i in range(5):
p.apply_async(long_time_task, args=(i,)) # 設置每個進程要執行的函數和參數,異步執行
print('Waiting for all subprocesses done...')
p.close() # 關閉進程池,不允許繼續添加新的 Process
p.join() # 等待全部子進程執行完畢
print('All subprocesses done.')
Parent process 274.
Run task 1 (431)...
Run task 0 (430)...
Waiting for all subprocesses done...
Task 1 runs 3.00 seconds.
Run task 2 (431)...
Task 0 runs 3.00 seconds.
Run task 3 (430)...
Task 2 runs 3.00 seconds.
Task 3 runs 3.00 seconds.
Run task 4 (431)...
Task 4 runs 3.00 seconds.
All subprocesses done.
1.3 進程間通信
multiprocessing 模塊封裝了底層的通信機制,提供了 Queue、Pipes 等多種方式來交換數據。以 Queue 為例,在父進程中創建兩個子進程,一個往 Queue 里寫數據,一個從 Queue 里讀數據。
import os, time, random
from multiprocessing import Process, Queue
def write(q): # 寫數據進程執行的代碼
print("Process to write: %s" % os.getpid())
for value in ['A', 'B', 'C']:
print("Put %s to queue..." % value)
q.put(value)
time.sleep(random.random())
def read(q): # 讀數據進程執行的代碼
print("Process to read: %s" % os.getpid())
while True:
value = q.get(True)
print("Get %s from queue." % value)
if __name__ == '__main__':
q = Queue() # 父進程創建Queue,并傳給各個子進程
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start() # 啟動子進程pw,寫入
pr.start() # 啟動子進程pr,讀取
pw.join() # 等待pw結束
pr.terminate() # pr進程里的死循環,無法等待結束,只能強制終止
Process to write: 211
Put A to queue...
Process to read: 212
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
2. 線程
線程是一種輕量進程,是 CPU 調度和分派的基本單元。線程并不產生新的地址空間和資源描述符表,而是復用父進程的。線程只擁有程序計數器、一組寄存器和棧,同一進程的線程共享其他全部資源。
線程由 OS 調度,相較于進程,線程調度的成本非常小。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。
2.1 解釋器
在談 Python 的線程之前,先了解下 Python 的幾個解釋器版本:
CPython ,Python 的官方版本,使用 C 語言實現,使用最為廣泛,大部分人使用的都是這個版本。
Jython,Python 的 Java 實現,相比于 CPython,與 Java 語言之間的互操作性要遠遠高于 CPython 和 C 語言之間的互操作性。
Python for .NET,CPython 實現的 .NET 托管版本,與 .NET 庫和程序代碼有很好的互操作性。
IronPython,不同于 Python for .NET,它是 Python 的 C# 實現,并且它將 Python 代碼編譯成 C# 中間代碼(與 Jython 類似),與.NET語言的互操作性也非常好。
PyPy,Python 的 Python 實現版本。PyPy 運行在 CPython(或者其它實現)之上,用戶程序運行在 PyPy 之上。目標是成為 Python 語言自身的試驗場,可以很容易地修改 PyPy 解釋器的實現(因為是使用Python寫的)。
Stackless,Stackless Python 是 CPython 的一個增強版本,它使程序員從基于線程的編程方式中獲得好處,并避免傳統線程所帶來的性能與復雜度問題。
2.2 全局鎖 GIL
GIL 是 CPython 中特有的全局解釋器鎖(其它 Python 版本解釋器,有自己的線程調度機制,沒有GIL機制)。本質上,GIL 就是 Python 進程中的一把超大鎖,在解釋器進程中是全局有效。GIL 主要鎖定的是 CPU 執行資源,實現線程獨占。
在 CPython 解釋器中,當一個線程需要使用 CPU 資源時,首先得獲取 GIL,直到遇到 I/O 操作時,才會釋放 GIL。
如果是 I/O 密集型線程,多線程能比單線程顯著提高性能;如果是 CPU 密集型線程,多線程并不能提高性能,因為等待 GIL,多線程也只能依次按順序執行。
在單核 CPU 中,同一時刻僅有一個線程占用 CPU,GIL 不會對 CPU 的使用率產生影響。但是在多核 CPU 中,由于 GIL 的存在,同一時刻,不同核的線程會競爭 GIL。獲取到 GIL 的線程能夠占用 CPU,而其他線程將處于閑置狀態,即使這些線程有空閑的 CPU 資源。
在 Python 3 中 GIL 也沒有去掉,因為有大量的第三方庫依賴 GIL。去掉 GIL 之后,需要引入復雜的鎖機制保護眾多全局狀態。
2.3 多線程示例
Python 的標準庫提供了兩個模塊:thread 和 threading,thread 是低級模塊,threading 是高級模塊,對 thread 進行了封裝。
import time, os, threading
start = time.time()
def doubler(number):
print(threading.currentThread().getName())
print('Parent process %s.' % os.getpid())
print(number * 2)
time.sleep(2)# 或者 IO 請求
print('thread run %0.2f s end'% (time.time() - start))
if __name__ == '__main__':
for i in range(3):
my_thread = threading.Thread(target=doubler, args=(i,))
my_thread.start()
#my_thread.join()
Thread-98
Parent process 426.
0
Thread-99
Parent process 426.
2
Thread-100
Parent process 426.
4
thread run 2.00 s end
thread run 2.01 s end
thread run 2.01 s end
由于線程中執行了 sleep ,釋放了 CPU 資源,其他線程得以執行。如果新增注釋部分的代碼?my_thread.join(), 那么線程將串行執行:
Thread-101
Parent process 426.
0
thread run 2.01 s end
Thread-102
Parent process 426.
2
thread run 4.01 s end
Thread-103
Parent process 426.
4
thread run 6.02 s end
2.4 multiprocessing.dummy
multiprocessing.dummy 模塊與 multiprocessing 模塊的區別: dummy 模塊是多線程,而 multiprocessing 是多進程, 調用方式相同。
from multiprocessing import Pool
from multiprocessing.dummy import Pool
與 multiprocessing 類似,dummy 模塊提供了多線程池,可以很方便將代碼在多線程和多進程之間切換。dummy 模塊在大量的開源項目中有所應用,十分推薦使用。
3. 協程
協程是一種輕量級的線程。協程擁有獨立的寄存器上下文和棧,同一個線程,共享堆。協程不由 OS 調度,OS 對于協程的一無所知,完全由程序員編碼進行控制。
具體點就是,執行函數 A 時,可以隨時中斷,去執行函數 B,接著中斷 B ,繼續執行函數A。而這些切換完全由程序吱聲控制。協程調度實際上是在同一線程中,進行程序函數的切換,沒有切換線程帶來的開銷。
協程比較適合處理 IO 密集型的任務。
3.1 Gevent
Gevent 是第三方庫,通過 Greenlet 實現協程,其基本實現原理是:
當一個 Greenlet 遇到 IO 操作時,比如訪問網絡,就自動切換到其他的 Greenlet,等到 IO 操作完成,再在適當的時候切換回來繼續執行。由于 IO 操作非常耗時,經常使程序處于等待狀態,有了 Gevent 為我們自動切換協程,就保證總有Greenlet 在運行,而不是等待 IO。
import gevent
import time, os, threading
from gevent import monkey;
monkey.patch_all() # 將默認阻塞的模塊替換成非阻塞
start = time.time()
def doubler(number):
print('Parent process %s.' % os.getpid())
print(number * 2)
time.sleep(2)
print('run %0.2f s end'% (time.time() - start))
if __name__ == '__main__':
tasks=[gevent.spawn(doubler,i) for i in range(3)] # gevent.spawn 啟動協程,參數為函數名稱和參數名稱
gevent.joinall(tasks) # gevent.joinall 等待執行完畢
Parent process 871.
0
Parent process 871.
2
Parent process 871.
4
run 2.00 s end
run 2.00 s end
run 2.00 s end
從結果來看,Python 中多線程和多協程的效果類似,在當前執行阻塞時,切換執行流程。不同的是,多線程切換的是線程,而協程切換的是正在執行的函數上下文。
使用 Gevent,可以獲得極高的并發性能,但 Gevent 只能在 Unix/Linux下運行,在 Windows 下不保證正常安裝和運行。
3.2 Django
在 Django 中也會使用 Gevent 來增強并發能力,特別是對于 IO 密集型的請求較多時:
# 使用 uwsgi 部署
$ uwsgi --gevent 100 --gevent-monkey-patch --http :8000 -M --processes 4 --wsgi-file wsgi.py
# 使用 gunicorn 部署
$ gunicorn --worker-class=gevent wsgi:application -b 0.0.0.0:8000
3.3 Celery
Celery 支持幾種并發模式,有 prefork,threading,協程(gevent,eventlet)。在 Celery 中使用并發模式,能顯著提高處理效率,特別是 IO 操作較多時。
$ celery worker -A celery_worker.celery -P gevent -c 10 -l INFO
-P 選項指定 pool,默認是 prefork,這里指定為 gevent, -c 設置并發數。
4. 最佳實踐
IO 密集型的任務(例如,網絡調用等)中使用線程和協程
CPU 密集的任務,需要使用多個進程,繞開 GIL 限制,充分利用多核 CPU ,提高效率
為了充分利用 CPU ,可以結合多進程+協程進行部署,多個進程,每個進程中多個協程。
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的python 协程、进程、线程_Python 中的进程、线程、协程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python k线图 动态刷新不了_Im
- 下一篇: mysql主库宕机能写吗_MYSQL主主