【Python】编程笔记9
文章目錄
- 進程和線程
- 一、基礎知識
- 二、多進程(multiprocessing)
- 1、初體驗
- 2、Pool(進程池)
- (1)非阻塞
- (2)阻塞
- (4)代碼解讀
- (3)分析
- 3、子進程
- (1)開啟子進程
- (2)添加輸入——communicate()方法
- 4、進程間通信
- 三、多線程
- 1、啟動
- 2、Lock
- 3、多核CPU
- 4、ThreadLocal
- 四、進程 vs. 線程
- 1、多任務實現
- 2、線程切換
- 3、計算密集型 vs. IO 密集型
- 4、異步IO
- 五、分布式進程
進程和線程
對于操作系統來說,一個任務就是一個進程(Process)。
在一個進程內部,要同時干多件事,就需要同時運行多個“子任務”,將進程內的這些“子任務”稱為線程(Thread)。
==》如何同時執行多個任務?
- 方法1:多進程模式。 啟動多個進程,每個進程雖然只有一個線程,但是多個進程可以一塊執行多個任務。
- 方法2:多線程模式。 啟動一個進程,在一個進程內啟動多個線程,則多個線程可以一塊執行多個任務。
- 方法3:多進程+多線程模式。啟動多個進程,每個進程再啟動多個線程==》模型復雜,很少采用。
- 注意:還需考慮相互通信和協調、同步、數據共享的問題。
一、基礎知識
Unix/Linux系統中的 fork() 系統調用,fork() 調用一次,返回兩次。(操作系統自動將當前進程(父進程)復制一份(子進程),然后分別在父進程和子進程內返回。)
**子進程永遠返回 0,而父進程返回子進程的 ID。**子進程調用 getppid() 就可以獲得父進程的 ID。
import osprint('Process (%s) start...' % os.getpid()) ## 僅在Unix/Linux系統下 pid = os.fork() if pid == 0:print("I am child process (%s) and my parent is %s." % (os.getpid(), os.getppid())) else:print("I (%s) just created a child process (%s)." % (os.getpid(), pid))輸出結果
Process (876) start... I (876) just created a child process (877). I am child process (877) and my parent is 876.- os.getpid()——獲取當前進程的 ID
- os.getppid()——獲得當前進程的父進程的 ID
==》fork 調用可以在一個進程在接到新任務時就可以復制出一個子進程來處理新任務。
二、多進程(multiprocessing)
模塊:multiprocessing——跨平臺
1、初體驗
Process 類來代表一個進程對象,創建實例對象時,需要傳入執行函數和執行函數的參數;
用start()方法啟動進程實例;
join()方法可以等待子進程結束后再繼續往下執行,常用于進程間的同步。
輸出結果
Parent process 10808. Child process will start. Run child process test (13692)... Child process end.2、Pool(進程池)
用進程池(Pool)的方式批量創建子進程。
Pool 的默認大小是 CPU 的核數
(1)非阻塞
import multiprocessing, os import timedef func(msg):print("msg:", msg, "(%s)" % os.getpid())start = time.time()time.sleep(3)end = time.time()print("Task %s end %f" % (msg, (end - start)))if __name__ == '__main__':print('Parent process %s.' % os.getpid())pool = multiprocessing.Pool(processes = 3)for i in range(3):msg = "hello %d" % (i)pool.apply_async(func, (msg,))pool.close()pool.join()print("Sub-process(es) done.")輸出結果
Parent process 1000. msg: hello 0 (4416) msg: hello 1 (14240) msg: hello 2 (828) Task hello 0 end 3.000664 Task hello 1 end 3.000952 Task hello 2 end 3.000939 Sub-process(es) done.(2)阻塞
import multiprocessing, time, osdef func(msg):print("msg:", msg, "(%s)" % os.getpid())start = time.time()time.sleep(3)end = time.time()print("Task %s end %f" % (msg, (end - start))) if __name__ == '__main__':print('Parent process %s.' % os.getpid())pool = multiprocessing.Pool(processes = 3)for i in range(3):msg = "hello %d" % (i)pool.apply(func, (msg,))pool.close()pool.join()print("Sub-process(es) done.")輸出結果
Parent process 244. msg: hello 0 (10280) Task hello 0 end 3.000280 msg: hello 1 (15200) Task hello 1 end 3.000413 msg: hello 2 (10140) Task hello 2 end 3.000886 Sub-process(es) done.(4)代碼解讀
對 Pool 對象調用 join() 方法會等待所有子進程執行完畢,調用 join() 之前必須先調用 close(),調用 close()之后就不能繼續添加新的 Process 了。
(3)分析
區別主要是 apply_async和 apply函數,前者是非阻塞的,后者是阻塞。非阻塞多個子進程可以同時進行,而阻塞子進程依次進行。
3、子進程
子進程是一個外部進程,需要控制其輸入和輸出。主要功能是執行外部的命令和程序。
==》模塊:subprocess
subprocess包中定義有數個創建子進程的函數,這些函數分別以不同的方式創建子進程,所以我們可以根據需要來從中選取一個使用。另外subprocess還提供了一些管理標準流(standard stream)和管道(pipe)的工具,從而在進程間使用文本通信。
使用subprocess包中的函數創建子進程的時候,要注意:
- 在創建子進程之后,父進程是否暫停,并等待子進程運行。
- 函數返回什么
- 當returncode不為0時,父進程如何處理。
(1)開啟子進程
- subprocess.call()
- subprocess.check_call()
- subprocess.check_output()
參數:命令字符串,eg:([‘ping’,‘www.baidu.com’,’-c’,‘3’]) 或 (“ping www.baidu.com -c 3”) 兩種形式。在Windows環境下,最好添加 shell=True 參數,使得可以順利地執行dos命令。
區別:返回值。子進程的執行返回碼;若返回碼是0則返回0,否則出錯的話raise起CalledProcessError,可以用except處理之;若返回碼是0則返回子進程向stdout輸出的結果,否則也raise起CalledProcessError。
三種方法均會讓父進程掛起等待,在子進程結束之前,父進程不會繼續執行下去。
本質:對 subprocess.Popen 方法的封裝,Popen 開啟的子進程不會讓父進程等待其完成的,除非調用 wait() 方法。
import subprocess print('$ nslookup www.python.org') r = subprocess.call(['nslookup','www.python.org']) print('Exit coe:' ,r)結果輸出
$ nslookup www.python.org ��?��?��: ������: UnKnown Address: 192.168.43.1����: dualstack.python.map.fastly.net Addresses: 2a04:4e42:6::223151.101.24.223 Aliases: www.python.orgExit coe: 0(2)添加輸入——communicate()方法
## 有輸入的子進程 print('$ nslookup') p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = p.communicate(b'set q=mx\npython.org\nexit\n') print(output.decode('gbk')) # returncode 子進程的退出狀態 print('Exit code:', p.returncode)結果輸出
$ nslookup 默認服務器: UnKnown Address: 192.168.43.1> > 服務器: UnKnown Address: 192.168.43.1python.org MX preference = 50, mail exchanger = mail.python.org > Exit code: 0stdin, stdout 和 stderr:指定了執行程序的標準輸入,標準輸出和標準錯誤的文件句柄。它們的值可以是PIPE, 一個存在的文件描述符(正整數),一個存在的文件對象,或 None。
4、進程間通信
多種機制:Queue、Pipes等方式交換數據。
示例:Queue為例,在父進程中創建兩個子進程,一個往 Queue 里寫數據,一個從 Queue 里讀數據。
from multiprocessing import Process, Queue import os, time, random# 寫數據進程執行的代碼 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__':# 父進程創建 Queue,并傳給各個子進程q = Queue()pw = Process(target=write, args=(q,))pr = Process(target=read, args=(q, ))# 啟動子進程 pw,寫入pw.start()# 啟動子進程 pr,讀取pr.start()# 等待 pw 結束pw.join()# pr 進程里是死循環,無法等待其結束,只能強行終止pr.terminate()結果輸出
Process to write: 3564 Put A to queue... Process to read: 2500 Get A from queue. Put B to queue... Get B from queue. Put C to queue... Get C from queue.三、多線程
一個進程內包含若干線程(至少有一個線程)。
線程是操作系統直接支持的執行單元 ==》高級語言內置多線程支持。Python中為真正的 Posix Thread
模塊:_thread 和 threading
- _thread:低級模塊
- threading:對_thread的封裝,最常用。
1、啟動
啟動一個線程就是把一個函數傳入并創建 Thread 實例,然后調用 start() 開始執行。
import time, threading# 新線程執行的代碼 def loop():print('Thread %s is running...' % threading.current_thread().name)n = 0while n < 5:n = n + 1print('Thread %s >>> %s' % (threading.current_thread().name, n))time.sleep(1)print('Thread %s ended.' % threading.current_thread().name)print('Thread %s is running...' % threading.current_thread().name) ## 創建子線程 t = threading.Thread(target=loop, name='LoopThread') ## 啟動子線程 t.start() t.join() print('Thread %s ended.' % threading.current_thread().name)結果輸出
Thread MainThread is running... Thread LoopThread is running... Thread LoopThread >>> 1 Thread LoopThread >>> 2 Thread LoopThread >>> 3 Thread LoopThread >>> 4 Thread LoopThread >>> 5 Thread LoopThread ended. Thread MainThread ended.分析
- 任何進程默認就會啟動一個線程(主線程,name:MainThread),主線程又可以啟動新的線程。
- threading.current_thread()函數,返回當前線程的實例。
- 子線程的名字在創建時指定,eg:LoopThread。若不指定,則自動給線程命名為 Thread-1、Thread-2…
2、Lock
進程與線程最大的區別
- 多進程中,同一變量,各自有一份拷貝存于每個進程,互不影響;
- 多線程中,所有變量都由所有線程共享。==》任何一個變量都可以被任何一個線程修改。
原因:高級語言的一條語句在CPU執行時是若干條語句,而執行這幾條語句中時,線程可能中斷,從而導致多個線程把同一個對象的內容改亂了。
==》解決方法:threading.Lock() 函數。
結果輸出:0
分析:當多個線程同時執行 lock.acquire()時,只有一個線程能成功地獲取鎖,然后繼續執行代碼,其他線程就繼續等待直到獲得鎖為止。
注意:獲得鎖的線程使用完要釋放鎖,否則會造成死線程,推薦使用 try…finally 方式。
缺點:
- 阻止多線程并發的執行:包含鎖的某段代碼實際只能單線程模式執行,效率低;
- 存在多個鎖時,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能造成死鎖,導致多個線程全部掛起,既不能執行,也無法結束,只能靠操作系統強制終止。
3、多核CPU
多核CPU遇到死循環時:
- CPU使用率:一個死循環線程會100%占用一個CPU;兩個則會占到200%CPU(兩個CPU核心)==》要想把 N 核 CPU 的核心全部跑滿,就必須啟動 N 個死循環線程。
在Python中,當解釋器執行代碼時,有個 GIL鎖(Global Interpreter Lock)。任何 Python 線程執行前,先必須獲得GIL鎖,然后,每執行100條字節碼,解釋器就自動釋放 GIL 鎖,讓其他的線程有機會執行。
==》多線程在Python中只能交替運行,也只能用到一個核。
==》可以使用多線程,但不能有效利用多核,除非通過C擴展實現。
==》Python 可利用多進程實現多任務。多個進程有自己獨立的 GIL 鎖,互不影響。
4、ThreadLocal
ThreadLocal 最常用于 為每個線程綁定一個數據庫連接、HTTP請求、用戶身份信息等。
==》一個線程的所有調用到的處理函數都可以訪問這些資源。
結果輸出
Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)四、進程 vs. 線程
1、多任務實現
通常設計 Master-Worker 模式,Master 負責分配任務,Worker 負責執行任務。
==》一個 Master、多個 Worker
若用多進程實現 Master-Worker,主進程是 Master,其他進程是 Worker;
==》穩定性高。(一個子進程崩潰了,不會影響主進程和其他子進程。當然主進程掛了所有進程就全掛了,但是Master 進程只負責分配任務,掛掉的概率低,eg:Apache)
==》創建進程的代價大。 Windows下開銷巨大。
若用多線程實現 Master-Worker,主線程是 Master,其他線程是 Worker。
==》稍快,效率高(Windows下IIS服務區默認采用多線程模式)
==》致命缺點:任何一個線程掛掉都可能直接造成整個進程崩潰(所有線程共享進程的內存)。
2、線程切換
單任務模式(批處理任務模式):處理完任務A,再處理任務 B …
多任務模式:涉及到任務的切換。操作系統在切換進程或
者線程時也是一樣的,它需要先保存當前執行的現場環境( CPU 寄存器狀態、內存頁等),然后,把新任務的執行環境準備好(恢復上次的寄存器狀態,切換內存頁等),才能開始執行。
==》任務過多,會造成系統處于假死的狀態。
3、計算密集型 vs. IO 密集型
計算密集型任務
- 需要大量的計算,消耗CPU資源。==》強調:代碼效率(C語言)
- 雖可以用多任務完成,但任務越多,切換所需的時間就越多,CPU執行任務的效率就越低 ==》高效:計算密集型任務同時進行的數量等于CPU的數量。
IO 密集型任務
- 涉及網絡、磁盤IO的任務均為IO 密集型任務;
- CPU 消耗少,大多數時間(99%)是等待IO操作完成。
- 任務越多,CPU效率越高==》強調:開發效率(Python)
4、異步IO
可實現:用單進程單線程模型執行多任務,稱為事件驅動模型
五、分布式進程
Thread vs. Process
- Process 更穩定,可分布到多臺機器上;
- Thread 最多只能分布到同一臺機器的多個 CPU 上。
multiprocessing.managers 模塊
- 支持把多進程分布到多臺機器上。一個服務進程為調度者,通過網絡通信將任務分布到其他多個進程中。
待完善。。。
總結
以上是生活随笔為你收集整理的【Python】编程笔记9的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 金融贷款逾期的模型构建4——模型调优
- 下一篇: 【Python】编程笔记10