Linux 多行程
等等!!! 台灣大部份的人都使用Windows, 使用Linux的人反而倒是沒幾個, 那為什麼要講解Linux的多行程, 不講Windows呢. 這是因為Windows 的核心是黑箱, 沒人知道, 所以除了微軟官方外, 沒有一個人說的準.
Linux開機完成後, 第一個執行的Process稱為init 行程. 它提供一個很特殊的函數 fork(). 這函數很特殊, 是專門用來產生新的行程, 它會返回二次.
一開始, 由init調用fork()後, 就會把init 複制一份出來, 並重新命名, 比如A, 這樣就產生了第二支Process. 此時在init 裏的fork當然也會被複製, 所以 A裏當然也有一份fork函數. init裏的fork會傳回新行程 A 的id給init, 而A裏的fork則傳回 0 給 A. init稱為父行程, 而A是init的子行程.
這樣說起來, 是因為Process進行分裂進而產生二個fork的啊, 所以當然會返回二次嘛, 這有什麼特殊的呢. 其實並沒有什麼特殊的, 特殊這二個字只是說給不懂的人聽的而以.
與其說fork是 “特殊”, 倒不如說fork像病毒一樣, 會複制自已進行分裂. 這樣的說法還比較正確跟貼切. (其實正常細胞也是這樣分裂的)
子行程如何得知自己的id呢. 在子行程裏只要調用getpid(), 就會向父行程詢問自己的id的.
創造新行程
請注意, 下面的程式碼只能在Linux執行. Window沒有fork的技術, 所以會產生例外
import os print('current Process id %s' % os.getpid()) print('Starting a new Process.......') child_id=os.fork() if child_id==0: print('I am a child process : %s, parent is %s' % (os.getpid(), os.getppid())) else: print('I am parent : %s, new child process is %s' % (os.getpid(), child_id)) 結果 : current Process id 14885 Starting a new Process....... I am parent : 14885, new child process is 14886 I am a child process : 14886, parent is 14885
multiprocessing
即然在Windows下無法使用fork(), 那該怎麼辦呢, 還好在multiprocessing模組有一個Process類別, 可以當成行程的物件, 用來啟動新的子行程
下面代碼中, 利用Process函數啟動新行程, 並利用target指定要執行的函數. 但注意喔, Windows的子行程會執行本程式的其他部份, 但Linux不會. 所以為了防止Windows又進來, 就需用
if __name__==’__main__’ 限制子行程執行
另外, p.join(), 表示main process會等待子行程執行完畢, 才會再繼續main Process
import os
from multiprocessing import Process
def runon_child(name, job):
print('Child %s will process : %s, id: %s' % (name, job, os.getpid()))
if __name__=='__main__':
print('Parent process id : %s' % os.getpid())
p=Process(target=runon_child, args=('Kenneth','go home'))
print('Child will start.......')
p.start();
p.join();
print('Child process end, Continue main process');
else:
print('id : %s, name : %s' % (os.getpid(),__name__))
結果 :
Linux
-----------------------------------------------------------------
Parent process id : 15486
Child will start.......
Child Kenneth will process : go home, id: 15487
Child process end, Continue main process
Windows:
------------------------------------------------------------------
Parent process id : 5956
Child will start.......
id : 12464, name : __mp_main__
Child Kenneth will process : go home, id: 12464
Child process end, Continue main process
Pool
Pool行程池, 可以大量創造出子行程. 不過本物件只能用在Linux的版本, 使用在Windows下一直無法進入新的行程.
p=Pool(3) 是指一次可以同時執行3個子行程, 待某個行程結束了, 再抓其他的行程進來執行. 而當使用 p.apply_async()加入子行程完畢後, 需使用p.close()關閉, 表示不可以再加其他的行程進來了.
from multiprocessing import Pool import time, os, random def child_task(name): print("Process : %s(%s)......" % (name, os.getpid())) t1=time.time() time.sleep(random.random()*3) t2=time.time() print("(%s)Total Spent : %f s" % (os.getpid(), t2-t1)) if __name__== '__main__': print("Main process : %s" % os.getpid()) p=Pool(3) for i in range(10): p.apply_async(child_task, args=(i,)) print("main Process waiting......") p.close() p.join() print("All child process is finished") 結果 : Main process : 6047 main Process waiting...... Process : 0(6048)...... Process : 1(6049)...... Process : 2(6050)...... Process : 3(6051)...... Process : 4(6052)...... Process : 5(6053)...... Process : 6(6054)...... Process : 7(6055)...... Process : 8(6056)...... Process : 9(6057)...... (6056)Total Spent : 0.013218 s (6050)Total Spent : 0.123727 s (6053)Total Spent : 0.290385 s (6057)Total Spent : 0.947474 s (6049)Total Spent : 1.838181 s (6054)Total Spent : 1.985953 s (6052)Total Spent : 2.143907 s (6055)Total Spent : 2.937980 s (6051)Total Spent : 2.977545 s (6048)Total Spent : 2.988172 s All child process is finished
subprocess
subprocess可以產生新的行程並執行檔案中的執行檔. 注意喔, 是執行檔喔. 如果是像DOS的 ‘dir’, 因為那是一個指令, 不是檔案執行檔, 所以無法執行. 而Linux的 ‘ls’, 是一支真真實實的檔案執行檔, 所以沒問題
import subprocess r=subprocess.call(['ls', '-alR']) r=subprocess.call(['ping', '168.95.1.1']) 結果 : drwxr-xr-x 12 thomas thomas 4096 Jun 3 03:46 . drwxr-xr-x 4 root root 4096 May 28 14:44 .. -rw------- 1 thomas thomas 7712 Jun 3 03:10 .bash_history -rw-r--r-- 1 thomas thomas 220 Apr 4 2018 .bash_logout -rw-rw-r-- 1 thomas thomas 41 May 29 00:35 .bash_profile -rw-r--r-- 1 thomas thomas 3812 May 29 00:34 .bashrc PING 168.95.1.1 (168.95.1.1) 56(84) bytes of data. 64 bytes from 168.95.1.1: icmp_seq=1 ttl=246 time=10.4 ms 64 bytes from 168.95.1.1: icmp_seq=2 ttl=246 time=10.0 ms 64 bytes from 168.95.1.1: icmp_seq=3 ttl=246 time=10.4 ms
行程間通訊
以下面的圖示說明. 首先由主行程產生二個子行程Process A及 Process B. A跟B之間的通訊, 以一個共通的Queue為溝通橋樑. A負責將資料寫入 Queue中, 然後 B 一直重複的從 Queue中將資料取出.
程式碼的部份, put_data這函數就交由Process A來執行, get_data 就交由Process B執行. 因為Process B使用了無窮迴圈, 所以待Process A執行完畢後, 在主行程還要下達processB.terminate()將B中斷
請注意, 在Windows之下, 因為沒有Queue, 所以會造成例外. 本程式需於Linux下執行
from multiprocessing import Process, Queue import time, os def put_data(queue): for value in [1,2,3,4,5]: print('%s : Put %s to queue' % (os.getpid(),value)) queue.put(value) time.sleep(2) def get_data(queue): while True: print('%s : Get %s from queue' % (os.getpid(), queue.get(True))) if __name__ == '__main__': queue=Queue() processA = Process(target=put_data, args=(queue,)) processB = Process(target=get_data, args=(queue,)) processA.start() processB.start() processA.join() processB.terminate() 結果 : 8257 : Put 1 to queue 8258 : Get 1 from queue 8257 : Put 2 to queue 8258 : Get 2 from queue 8257 : Put 3 to queue 8258 : Get 3 from queue 8257 : Put 4 to queue 8258 : Get 4 from queue 8257 : Put 5 to queue 8258 : Get 5 from queue