add : 在fork多線程的進程時,創建的子進程只包含一個線程,該線程是調用fork函數的那個線程的副本。在man fork中,有The child process is created with a single thread—the one that called fork().這句話,親測的確如此。在多線程進程中,為了多線程的同步及互斥,會有鎖,在fork時,這些鎖會一同fork到子進程中,這會導致一些問題,見下文。個人建議,最好不要fork多線程的進程,除非你有能力解決這個問題。在python的multiprocessing庫中,就fork了多線程的進程。Queue中使用了線程將入隊的消息放入管道,如果父進程使用了Queue.put(),那用Process()類創建子進程時,就會fork Queue類,但不會fork它里面的線程。multiprocessing的Process()創建子進程應該是解決上面提到的fork多線程進程時,鎖相關的問題
import datetime import time import threading import os import thread from multiprocessing import Process def print_thread(*args): st = datetime.datetime.now() while True: now = datetime.datetime.now() print 'thread, now={}, tid={}, pid={}'.format(str(now), thread.get_ident(), os.getpid()) time.sleep(1) if now - st > datetime.timedelta(minutes=30): break def print_proc(*args): st = datetime.datetime.now() while True: now = datetime.datetime.now() print 'sub process, now={}, pid={}'.format(str(now), os.getpid(), os.getppid()) time.sleep(1) if now - st > datetime.timedelta(minutes=30): break if __name__ == '__main__': print 'main process, pid={}'.format(os.getpid()) t = threading.Thread(target=print_thread) t.start() time.sleep(2) print 'create sub process' p = Process(target=print_proc) p.start()
執行結果如下:
main process, pid=5442 thread, now=2018-01-01 19:30:19.570559, tid=139746090014464, pid=5442 thread, now=2018-01-01 19:30:20.576551, tid=139746090014464, pid=5442 create sub process thread, now=2018-01-01 19:30:21.584519, tid=139746090014464, pid=5442 sub process, now=2018-01-01 19:30:21.585514, pid=5448 thread, now=2018-01-01 19:30:22.586036, tid=139746090014464, pid=5442 sub process, now=2018-01-01 19:30:22.586514, pid=5448 thread, now=2018-01-01 19:30:23.587206, tid=139746090014464, pid=5442 sub process, now=2018-01-01 19:30:23.587485, pid=5448
原文:https://blog.codingnow.com/2011/01/fork_multi_thread.html
在 POSIX 標准中,fork 的行為是這樣的:復制整個用戶空間的數據(通常使用 copy-on-write 的策略,所以可以實現的速度很快)以及所有系統對象,然后僅復制當前線程到子進程。這里:所有父進程中別的線程,到了子進程中都是突然蒸發掉的。
其它線程的突然消失,是一切問題的根源。
我之前從未寫過多進程多線程程序,不過公司里有 David Xu 同學(他實現維護着 FreeBSD 的線程庫)是這方面的專家,今天跟徐同學討論了一下午,終於覺得自己搞明白了其中的糾結。嗯,寫點東西整理一下思路。
可能產生的最嚴重的問題是鎖的問題。
因為為了性能,大部分系統的鎖是實現在用戶空間的。所以鎖對象會因為 fork 復制到子進程中。
對於鎖來說,從 OS 看,每個鎖有一個所有者,即最后一次 lock 它的線程。
假設這么一個環境,在 fork 之前,有一個子線程 lock 了某個鎖,獲得了對鎖的所有權。fork 以后,在子進程中,所有的額外線程都人間蒸發了。而鎖卻被正常復制了,在子進程看來,這個鎖沒有主人,所以沒有任何人可以對它解鎖。
當子進程想 lock 這個鎖時,不再有任何手段可以解開了。程序發生死鎖。
為何,POSIX 指定標准時,會定下這么一個顯然不靠譜的規則?允許復制一個完全死掉的鎖?答案是歷史和性能。因為歷史上,把鎖實現在用戶態是最方便的(今天依舊如此)。背后可能只需要一條原子操作指令即可。大多數 CPU 都支持的。fork 只管用戶空間的復制,不會涉及其中的對象細節。
一般的慣例,多線程程序 fork 前,應該由發起 fork 的線程 lock 所有子進程可能用到的鎖,fork 后,把它們一一 unlock 。當然,這樣的做法就隱含了鎖的次序。如果次序和平時不同,那么就會死鎖。
不光是顯式的使用鎖,許多 CRT 函數也會間接的使用。比如 fprintf 這些文件操作。因為對 FILE * 的操作是依靠鎖來達到線程安全的。最常見的問題是在子線程里調用 fprintf 寫 log 。
除此之外,就是要小心一些不依賴鎖的數據一致性問題了。比如若在父進程里另一個線程中操作一個鏈表,fork 發生時,因為其它線程的突然消失,這個鏈表就可能會因為只操作了一半而是不完整的數據。不過這一般不會是問題,或者可以歸咎於對鎖的處理。(多個線程,訪問同一塊數據。比如一條鏈表。就是需要加鎖的)