被問懵了:一個進程最多可以創建多少個執行緒?
大家好,我是小林。
昨天有位讀者問了我這麼個問題:
大致意思就是,他看了一個面經,說虛擬記憶體是 2G 大小,然後他看了我的圖解系統 PDF 里說虛擬記憶體是 4G,然後他就懵逼了。
其實他看這個面經很有問題,沒有說明是什麼作業系統,以及是多少位作業系統。
因為不同的作業系統和不同位數的作業系統,虛擬記憶體可能是不一樣多。
Windows 系統我不了解,我就說說 Linux 系統。
在 Linux 作業系統中,虛擬地址空間的內部又被分為內核空間和用戶空間兩部分,不同位數的系統,地址 空間的範圍也不同。比如最常⻅的 32 位和 64 位系統,如下所示:
通過這裡可以看出:
- 32 位系統的內核空間佔用 1G ,位於最高處,剩下的 3G 是用戶空間;
- 64 位系統的內核空間和用戶空間都是 128T ,分別佔據整個記憶體空間的最高和最低處,剩下的中
間部分是未定義的。
接著,來看看讀者那個面經題目:一個進程最多可以創建多少個執行緒?
這個問題跟兩個東西有關係:
- 進程的虛擬記憶體空間上限,因為創建一個執行緒,作業系統需要為其分配一個棧空間,如果執行緒數量越多,所需的棧空間就要越大,那麼虛擬記憶體就會佔用的越多。
- 系統參數限制,雖然 Linux 並沒有內核參數來控制單個進程創建的最大執行緒個數,但是有系統級別的參數來控制整個系統的最大執行緒個數。
我們先看看,在進程里創建一個執行緒需要消耗多少虛擬記憶體大小?
我們可以執行 ulimit -a 這條命令,查看進程創建執行緒時默認分配的棧空間大小,比如我這台伺服器默認分配給執行緒的棧空間大小為 8M。
在前面我們知道,在 32 位 Linux 系統里,一個進程的虛擬空間是 4G,內核分走了1G,留給用戶用的只有 3G。
那麼假設創建一個執行緒需要佔用 10M 虛擬記憶體,總共有 3G 虛擬記憶體可以使用。於是我們可以算出,最多可以創建差不多 300 個(3G/10M)左右的執行緒。
如果你想自己做個實驗,你可以找台 32 位的 Linux 系統運行下面這個程式碼:
由於我手上沒有 32 位的系統,我這裡貼一個網上別人做的測試結果:
如果想使得進程創建上千個執行緒,那麼我們可以調整創建執行緒時分配的棧空間大小,比如調整為 512k:
$ ulimit -s 512
說完 32 位系統的情況,我們來看看 64 位系統里,一個進程能創建多少執行緒呢?
我的測試伺服器的配置:
- 64 位系統;
- 2G 物理記憶體;
- 單核 CPU。
64 位系統意味著用戶空間的虛擬記憶體最大值是 128T,這個數值是很大的,如果按創建一個執行緒需佔用 10M 棧空間的情況來算,那麼理論上可以創建 128T/10M 個執行緒,也就是 1000多萬個執行緒,有點魔幻!
所以按 64 位系統的虛擬記憶體大小,理論上可以創建無數個執行緒。
事實上,肯定創建不了那麼多執行緒,除了虛擬記憶體的限制,還有系統的限制。
比如下面這三個內核參數的大小,都會影響創建執行緒的上限:
- /proc/sys/kernel/threads-max,表示系統支援的最大執行緒數,默認值是
14553
; - /proc/sys/kernel/pid_max,表示系統全局的 PID 號數值的限制,每一個進程或執行緒都有 ID,ID 的值超過這個數,進程或執行緒就會創建失敗,默認值是
32768
; - /proc/sys/vm/max_map_count,表示限制一個進程可以擁有的VMA(虛擬記憶體區域)的數量,具體什麼意思我也沒搞清楚,反正如果它的值很小,也會導致創建執行緒失敗,默認值是
65530
。
那接下針對我的測試伺服器的配置,看下一個進程最多能創建多少個執行緒呢?
我在這台伺服器跑了前面的程式,其結果如下:
可以看到,創建了 14374 個執行緒後,就無法在創建了,而且報錯是因為資源的限制。
前面我提到的 threads-max
內核參數,它是限制系統里最大執行緒數,默認值是 14553。
我們可以運行那個測試執行緒數的程式後,看下當前系統的執行緒數是多少,可以通過 top -H
查看。
左上角的 Threads 的數量顯示是 14553,與 threads-max
內核參數的值相同,所以我們可以認為是因為這個參數導致無法繼續創建執行緒。
那麼,我們可以把 threads-max 參數設置成 99999
:
echo 99999 > /proc/sys/kernel/threads-max
設置完 threads-max 參數後,我們重新跑測試執行緒數的程式,運行後結果如下圖:
可以看到,當進程創建了 32326 個執行緒後,就無法繼續創建里,且報錯是無法繼續申請記憶體。
此時的上限個數很接近 pid_max
內核參數的默認值(32768),那麼我們可以嘗試將這個參數設置為 99999:
echo 99999 > /proc/sys/kernel/pid_max
設置完 pid_max 參數後,繼續跑測試執行緒數的程式,運行後結果創建執行緒的個數還是一樣卡在了 32768 了。
當時我也挺疑惑的,明明 pid_max 已經調整大後,為什麼執行緒個數還是上不去呢?
後面經過查閱資料發現,max_map_count
這個內核參數也是需要調大的,但是它的數值與最大執行緒數之間有什麼關係,我也不太明白,只是知道它的值是會限制創建執行緒個數的上限。
然後,我把 max_map_count 內核參數也設置成後 99999:
echo 99999 > /proc/sys/kernel/max_map_count
繼續跑測試執行緒數的程式,結果如下圖:
當創建差不多 5 萬個執行緒後,我的伺服器就卡住不動了,CPU 都已經被佔滿了,畢竟這個是單核 CPU,所以現在是 CPU 的瓶頸了。
我只有這台伺服器,如果你們有性能更強的伺服器來測試的話,有興趣的小夥伴可以去測試下。
接下來,我們換個思路測試下,把創建執行緒時分配的棧空間調大,比如調大為 100M,在大就會創建執行緒失敗。
ulimit -s 1024000
設置完後,跑測試執行緒的程式,其結果如下:
總共創建了 26390 個執行緒,然後就無法繼續創建了,而且該進程的虛擬記憶體空間已經高達 25T,要知道這台伺服器的物理記憶體才 2G。
為什麼物理記憶體只有 2G,進程的虛擬記憶體卻可以使用 25T 呢?
因為虛擬記憶體並不是全部都映射到物理記憶體的,程式是有局部性的特性,也就是某一個時間只會執行部分程式碼,所以只需要映射這部分程式就好。
你可以從上面那個 top 的截圖看到,雖然進程虛擬空間很大,但是物理記憶體(RES)只有使用了 400 多M。
好了,簡單總結下:
- 32 位系統,用戶態的虛擬空間只有 3G,如果創建執行緒時分配的棧空間是 10M,那麼一個進程最多只能創建 300 個左右的執行緒。
- 64 位系統,用戶態的虛擬空間大到有 128T,理論上不會受虛擬記憶體大小的限制,而會受系統的參數或性能限制。
絮叨絮叨
小林在 CSDN 寫了很多圖解網路和作業系統的系列文章,很高興收穫到很朋友的認可和支援,正好最近圖解網路和作業系統的文章連載的有 20+ 篇了,也算有個體系了。
所以為了方便大家閱讀,小林把自己原創的圖解網路和圖解作業系統整理成了 PDF,一整理後,沒想到每個圖解都輸出了 15 萬字 + 500 張圖,品質也是杠杠的,有很多朋友特地私信我,看了我的圖解拿到了大廠的offer。
圖解系統 PDF 開源下載:圖解系統 PDF 下載地址(點擊)
圖解網路 PDF 開源下載:圖解網路 PDF 下載地址(點擊)
我是小林,今天的你,比昨天更博學了嗎?