簡體   English   中英

當線程分叉時會發生什么?

[英]What happens when a thread forks?

我知道從線程調用fork() sys_call是個壞主意。 但是,如果一個線程使用fork()創建一個新進程會發生什么?

新進程將是創建線程的主線程的子進程。 我想。

如果其父進程首先完成,則新進程將附加到 init 進程。 它的父線程是主線程,而不是創建它的線程。

如果我錯了,請糾正我。

#include <stdio.h>
#include <pthread.h>

int main () 
{
     thread_t pid;
     pthread_create(&(pid), NULL, &(f),NULL);
     pthread_join(tid, NULL);
     return 0;
}

void* f()
{
     int i;
     i = fork();

     if (i < 0) {
         // handle error
     } else if (i == 0) // son process
     {
          // Do something;
     } else {
          // Do something;
     }
 }

新進程將是創建線程的主線程的子進程。 我想。

fork創建一個新進程。 進程的父進程是另一個進程,而不是線程。 所以新進程的父進程是舊進程。

請注意,子進程將只有一個線程,因為fork只復制調用fork的(堆棧的)線程。 (這並不完全正確:整個內存都是重復的,但子進程將只有一個活動線程。)

如果其父進程首先完成,則新進程將附加到 init 進程。

如果父進程首先完成,則向子SIGHUP發送SIGHUP信號。 如果子進程沒有因為SIGHUP而退出,它將被init為它的新父進程。 有關SIGHUP更多信息,另請參閱nohupsignal(7)的手冊頁。

它的父線程是主線程,而不是創建它的線程。

進程的父線程是一個進程,而不是一個特定的線程,所以說主線程或子線程是父線程是沒有意義的。 整個過程都是父進程。

最后一點:必須小心混合線程和叉子。 這里討論了一些陷阱。

但是,如果一個線程使用 fork() 創建一個新進程會發生什么?

將通過復制調用線程的地址空間(而不是進程的整個地址空間)來創建一個新進程。 這通常被認為是一個壞主意,因為很難做到正確。 POSIX 表示子進程(在多線程程序中創建)只能調用異步信號安全函數,直到它調用exec*函數之一。

如果其父進程首先完成,則新進程將附加到 init 進程。

子進程通常由 init 進程繼承。 如果父進程是一個控制進程(例如 shell),那么POSIX 需要

如果該進程是一個控制進程,SIGHUP 信號將被發送到屬於調用進程的控制終端的前台進程組中的每個進程。

但是,這對於大多數流程而言並非如此,因為大多數流程都不是控制流程。

它的父線程是主線程,而不是創建它的線程。

fork 子進程的父進程將始終是調用 fork() 的進程。 因此,PPID 是子進程,將是您程序的 PID。

如果我錯了,請糾正我。

會做:)

由於fork()是一個 POSIX 系統調用,它的行為是明確定義的:

一個進程應使用單個線程創建。 如果多線程進程調用 fork(),則新進程應包含調用線程及其整個地址空間的副本,可能包括互斥鎖和其他資源的狀態。 因此,為了避免錯誤,子進程可能只執行異步信號安全操作,直到調用 exec 函數之一。

https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html

分叉的孩子是其父級的完全副本,但只有在父級中調用fork()的線程仍然存在於子級中,並且是該子級的新主線程,直到您調用exec()

POSIX 解密“應使用單個線程創建”具有誤導性,因為實際上大多數實現將真正創建父進程的完全副本,因此所有其他線程及其內存也被復制,這意味着線程實際上在那里,它們只是不能再運行了,因為系統從不為它們分配任何 CPU 時間(它們在調度程序表中丟失)。

一個更簡單的心理形象如下:

當父進程調用 fork 時,整個進程會被凍結片刻,原子復制,然后父進程整體解凍,而在子進程中,只有調用 fork 的一個線程被解凍,其他一切都保持凍結狀態。

這就是為什么在fork()exec()之間執行某些系統調用不是省事的原因,正如 POSIX 標准所指出的那樣。 理想情況下,除了關閉或復制文件描述符、設置或恢復信號處理程序然后調用exec()之外,您不應該做更多的事情。

問題源於 fork(2) 本身的行為。 每當使用 fork(2) 創建新的子進程時,新進程都會獲得一個新的內存地址空間,但內存中的所有內容都是從舊進程復制的(寫時復制不是 100% 正確,但語義是相同的)。

如果我們在多線程環境中調用 fork(2) ,執行調用的線程現在是新進程中的主線程,而在父進程中運行的所有其他線程都已死。 他們所做的一切都與調用 fork(2) 之前完全一樣。

現在想象這些其他線程在調用 fork(2) 之前愉快地做着他們的工作,幾毫秒后它們就死了。 如果這些現在已死的線程所做的事情不打算完全保持原樣怎么辦?

讓我給你舉個例子。 假設我們的主線程(將調用 fork(2) 的那個)正在休眠,而我們有許多其他線程正在愉快地做一些工作。 分配內存、寫入內存、從中復制、寫入文件、寫入數據庫等等。 他們可能正在使用 malloc(3) 之類的東西分配內存。 好吧,事實證明 malloc(3) 在內部使用互斥鎖來保證線程安全。 而這正是問題所在。

如果其中一個線程正在使用 malloc(3) 並且在主線程調用 fork(2) 的同一時刻獲得了互斥鎖,該怎么辦? 在新的子進程中,鎖仍然被持有——由一個現在死了的線程持有,該線程永遠不會返回它。

新的子進程將不知道使用 malloc(3) 是否安全。 在最壞的情況下,它會調用 malloc(3) 並阻塞直到它獲得鎖,這永遠不會發生,因為應該返回它的線程已經死了。 這只是 malloc(3)。 想想數據庫驅動程序、文件處理庫、網絡庫等中所有其他可能的互斥鎖和鎖。

完整的解釋你可以通過這個 鏈接

Linux 內核本身沒有線程和進程的區別。 當進程派生時,它指定與父進程共享哪些內容(內存、打開的文件句柄等)。 但這只是一組標志。 線程和進程的概念應用於內核實現之上。

當然,大多數人通過 libc 調用內核,后者根據線程/進程的常見概念選擇標志。

在操作系統級別分叉線程與分叉進程相同。 這是 UNIX 實現之間的(棘手的)差異之一。 例如,一些 UNIX 確實有線程的概念——然后他們最終提出了一個問題:如果我分叉一個進程,我是否在新進程中復制它的所有線程? 但是對於linux,線程和進程本質上是相同的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM