<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      謹慎使用多線程中的fork

      2015-02-07 19:23  origins  閱讀(24927)  評論(5)    收藏  舉報

      前言

      在單核時代,大家所編寫的程序都是單進程/單線程程序。隨著計算機硬件技術的發展,進入了多核時代后,為了降低響應時間,重復充分利用多核cpu的資源,使用多進程編程的手段逐漸被人們接受和掌握。然而因為創建一個進程代價比較大,多線程編程的手段也就逐漸被人們認可和喜愛了。

      記得在我剛剛學習線程進程的時候就想,為什么很少見人把多進程和多線程結合起來使用呢,把二者結合起來不是更好嗎?現在想想當初真是too young too simple,后文就主要討論一下這個問題。

      進程與線程模型

      進程的經典定義就是一個執行中的程序的實例。系統中的每個程序都是運行在某個進程的context中的。context是由程序正確運行所需的狀態組成的,這個狀態包括存放在存儲器中的程序的代碼和數據,它的棧、通用目的寄存器的內容、程序計數器(PC)、環境變量以及打開的文件描述符的集合。

      進程主要提供給上層的應用程序兩個抽象:

      • 一個獨立的邏輯控制流,它提供一個假象,好像我們程序獨占的使用處理器。
      • 一個私有的虛擬地址空間,它提供一個假象,好像我們的程序獨占的使用存儲器系統。

      線程,就是運行在進程context中的邏輯流。線程由內核自動調度。每個線程都有它自己的線程context,包括一個唯一的整數線程ID、棧、棧指針、程序計數器(PC)、通用目的寄存器和條件碼。每個線程和運行在同一進程內的其他線程一起共享進程context的剩余部分。這包括整個用戶虛擬地址空間,它是由只讀文本(代碼)、讀/寫數據、堆以及所有的共享庫代碼和數據區域組成。線程也同樣共享打開文件的集合。

      即進程是資源管理的最小單位,而線程是程序執行的最小單位。

      在linux系統中,posix線程可以“看做”為一種輕量級的進程,pthread_create創建線程和fork創建進程都是在內核中調用__clone函數創建的,只不過創建線程或進程的時候選項不同,比如是否共享虛擬地址空間、文件描述符等。

      fork與多線程

      我們知道通過fork創建的一個子進程幾乎但不完全與父進程相同。子進程得到與父進程用戶級虛擬地址空間相同的(但是獨立的)一份拷貝,包括文本、數據和bss段、堆以及用戶棧等。子進程還獲得與父進程任何打開文件描述符相同的拷貝,這就意味著子進程可以讀寫父進程中任何打開的文件,父進程和子進程之間最大的區別在于它們有著不同的PID。

      但是有一點需要注意的是,在Linux中,fork的時候只復制當前線程到子進程,在fork(2)-Linux Man Page中有著這樣一段相關的描述:

      The child process is created with a single thread--the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

      也就是說除了調用fork的線程外,其他線程在子進程中“蒸發”了。

      這就是多線程中fork所帶來的一切問題的根源所在了。

      互斥鎖

      互斥鎖,就是多線程fork大部分問題的關鍵部分。

      在大多數操作系統上,為了性能的因素,鎖基本上都是實現在用戶態的而非內核態(因為在用戶態實現最方便,基本上就是通過原子操作或者之前文章中提到的memory barrier實現的),所以調用fork的時候,會復制父進程的所有鎖到子進程中。

      問題就出在這了。從操作系統的角度上看,對于每一個鎖都有它的持有者,即對它進行lock操作的線程。假設在fork之前,一個線程對某個鎖進行的lock操作,即持有了該鎖,然后另外一個線程調用了fork創建子進程。可是在子進程中持有那個鎖的線程卻"消失"了,從子進程的角度來看,這個鎖被“永久”的上鎖了,因為它的持有者“蒸發”了。

      那么如果子進程中的任何一個線程對這個已經被持有的鎖進行lock操作話,就會發生死鎖。

      當然了有人會說可以在fork之前,讓準備調用fork的線程獲取所有的鎖,然后再在fork出的子進程的中釋放每一個鎖。先不說現實中的業務邏輯以及其他因素允不允許這樣做,這種做法會帶來一個問題,那就是隱含了一種上鎖的先后順序,如果次序和平時不同,就會發生死鎖。

      如果你說自己一定可以按正確的順序上鎖而不出錯的話,還有一個隱含的問題是你所不能控制的,那就是庫函數。

      因為你不能確定你所用到的所有庫函數都不會使用共享數據,即他們都是完全線程安全的。有相當一部分線程安全的庫函數都是在內部通過持有互斥鎖的方式來實現的,比如幾乎所有程序都會用到的C/C++標準庫函數malloc、printf等等。

      比如一個多線程程序在fork之前難免會分配動態內存,這就必然會用到malloc函數;而在fork之后的子進程中也難免要分配動態內存,這也同樣要用到malloc,可這卻是不安全的,因為有可能malloc內部的鎖已經在fork之前被某一個線程所持有了,而那個線程卻在子進程中消失了。

      exec與文件描述符

      按照上文的分析,似乎多線程中在fork出的子進程中立刻調用exec函數是唯一明智的選擇了,其實即使這樣做還是有一點不足。因為子進程會繼承父進程中所有已打開的文件描述符,所以在執行exec之前子進程仍然可以讀寫父進程中的文件,但如果你不希望子進程能讀寫父進程里的某個已打開的文件該怎么辦?

      或許fcntl設置文件屬性是一種辦法:

      int fd = open("file", O_RDWR | O_CREAT);
      if (fd < 0)
      {
          perror("open");
      }
      fcntl(fd, F_SETFD, FD_CLOEXEC);

      但是如果在open打開file文件之后,調用fcntl設置CLOEXEC屬性之前有其他線程fork出了子進程了的話,這個子進程仍然是可以讀寫file文件。如果用鎖的話,就又回到了上文所討論的情況了。

      從Linux 2.6.23版本的內核開始,我們可以在open中設置O_CLOEXEC標志了,相當于“打開文件再設置CLOEXEC”成為了一個原子操作。這樣在fork出的子進程執行exec之前就不能讀寫父進程中已打開的文件了。

      pthread_atfork

      如果你不幸真的碰到了一個要解決多線程中fork的問題的時候,可以嘗試使用pthread_atfork:

      int pthread_atfork(void (*prepare)(void), void (*parent)void(), void (*child)(void));
      • prepare處理函數由父進程在fork創建子進程前調用,這個函數的任務是獲取父進程定義的所有鎖。
      • parent處理函數是在fork創建了子進程以后,但在fork返回之前在父進程環境中調用的。它的任務是對prepare獲取的所有鎖解鎖。
      • child處理函數在fork返回之前在子進程環境中調用,與parent處理函數一樣,它也必須解鎖所有prepare中所獲取的鎖。

      因為子進程繼承的是父進程的鎖的拷貝,所有上述并不是解鎖了兩次,而是各自獨自解鎖。可以多次調用pthread_atfork函數從而設置多套fork處理程序,但是使用多個處理程序的時候。處理程序的調用順序并不相同。parent和child是以它們注冊時的順序調用的,而prepare的調用順序與注冊順序相反。這樣可以允許多個模塊注冊它們自己的處理程序并且保持鎖的層次(類似于多個RAII對象的構造析構層次)。

      需要注意的是pthread_atfork只能清理鎖,但不能清理條件變量。在有些系統的實現中條件變量不需要清理。但是在有的系統中,條件變量的實現中包含了鎖,這種情況就需要清理。但是目前并沒有清理條件變量的接口和方法。

      結語

      • 在多線程程序中最好只用fork來執行exec函數,不要對fork出的子進程進行其他任何操作。
      • 如果確定要在多線程中通過fork出的子進程執行exec函數,那么在fork之前打開文件描述符時需要加上CLOEXEC標志。

      參考文獻

      1. Randal E.Bryant, David O'Hallaron. 深入理解計算機系統(第2版). 機械工業出版社, 2010
      2. W.Richard Stevens. UNIX環境高級編程(第3版), 人民郵電出版社, 2014
      3. Linux Man Page. fork(2)
      4. Damian Pietras. Threads and fork(): think twice before mixing them, 2009
      5. 云風. 極不和諧的 fork 多線程程序, 2011

      (完)

      主站蜘蛛池模板: 日韩中文字幕亚洲精品一| 激情综合五月丁香亚洲| 国产欧美精品一区二区三区| 国产av一区二区麻豆熟女| 国产精品久久久久影院老司| 色视频不卡一区二区三区| 野花社区在线观看视频| 国产精品成人午夜久久| 丁香五月婷激情综合第九色 | 久久青青草原国产精品最新片 | 国产精品人成视频免费播放| 国内精品伊人久久久久AV一坑| 四虎精品永久在线视频| 亚洲熟妇在线视频观看| 一个人看的www视频免费观看| 九九热免费在线观看视频| 日本大片在线看黄a∨免费| 亚洲午夜av一区二区| 亚洲精品在线二区三区| 南平市| 亚洲欧洲自拍拍偷午夜色| 久久亚洲精品11p| 欧美高清freexxxx性| 精品国产午夜福利在线观看| 视频一区视频二区在线视频| 激情无码人妻又粗又大| 亚洲欧美日韩综合一区二区| 无码AV无码免费一区二区| 熟女视频一区二区在线观看| 亚洲中文字幕精品第三区| 国产精品露脸3p普通话| 国内自拍小视频在线看| 久久精品国产久精国产| 国产精品永久免费无遮挡| 久爱www人成免费网站| 国偷自产一区二区三区在线视频| 亚洲中文无码av永久不收费| 国产精品亚洲аv无码播放| 亚洲电影天堂av2017| 亚洲欧美日本久久网站| 亚洲精品揄拍自拍首页一|