大家好,我是小林。
昨天有位讀者問了我這麼個問題:
大緻意思就是,他看了一個面經,說虛拟記憶體是 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 下載下傳位址(點選)
我是小林,今天的你,比昨天更博學了嗎?