fork多線程進程時的坑(轉)


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 發生時,因為其它線程的突然消失,這個鏈表就可能會因為只操作了一半而是不完整的數據。不過這一般不會是問題,或者可以歸咎於對鎖的處理。(多個線程,訪問同一塊數據。比如一條鏈表。就是需要加鎖的)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM