成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何有效控制 Go 線程數?

開發 后端
相信對 Go 有所了解的人,對下圖所示的 GMP 模型不會陌生,每個 P 都會有一個操作系統線程 M 來執行其上的 G。

[[431064]]

前陣子,在讀者交流群中有人提到 Go 默認設置的最大線程數的問題:如果超過一萬個 G (掛載于 M 上)阻塞于系統調用,那么程序就會被掛掉。

這是對的,因為 Go 對運行時創建的線程數量有一個限制,默認是 10000 個線程。今天我們就來探討一下 Go 線程數相關的問題。

閑置線程

相信對 Go 有所了解的人,對下圖所示的 GMP 模型不會陌生,每個 P 都會有一個操作系統線程 M 來執行其上的 G。

GMP 模型

我們可以通過設置 GOMAXPROCS 來設定 P 的最大值,這個值代表什么含義呢?

The GOMAXPROCS variable limits the number of operating system threads that

can execute user-level Go code simultaneously. There is no limit to the number of threads

that can be blocked in system calls on behalf of Go code; those do not count against

the GOMAXPROCS limit. This package's GOMAXPROCS function queries and changes

the limit.

通過 GOMAXPROCS 的定義文檔,我們可以看到該變量只是限制了可以同時執行用戶級 Go 代碼的 OS 系統線程數量(通俗地講:Go 程序最多只能有和 P 相等個數的系統線程同時運行)。但是,在系統調用中被阻塞的線程不在此限制之中。

對于系統調用,可分為同步和異步兩種方式。

我們在《Go 網絡編程和 TCP 抓包實操》一文中闡述的 Go 網絡編程模型,就是一種異步系統調用。它使用網路輪詢器進行系統調用,調度器可以防止 G 在進行這些系統調用時阻塞 M。這可以讓 M 繼續執行其他的 G,而不需要創建新的 M。

但是,如果 G 要進行的是無法異步完成的系統調用時怎么辦?當網絡輪詢器無法使用時,進行系統調用的 G 將會阻塞 M。在 Linux 下基于普通文件(Linux 下的 epoll 只支持 socket,Windows 下的 iocp 可以支持 socket、file)的系統調用就是一個典型的例子。

同步系統調用 1

如上圖所示,運行在 M1 上的 G1 想要請求一個同步系統調用。

同步系統調用 2

當發生同步系統調用并阻塞時,調度器將 M1 和仍然掛載在其之上的 G1 與 P 分離,并引入新的 M2 來運行 P 上的其他 G。

同步系統調用 3

當 G1 進行的阻塞式系統調用結束時,G1 重新回到 P 的 LRQ 中去,但 M1 變成了閑置線程,不會被回收,以留備后續復用。

問題來了,如果在某一短時段內,Go 程序存在大量無法短暫結束的同步系統調用,那線程數豈不是會一直漲下去?

最大線程數限制

線程數限制的問題,在官方 issues#4056: "runtime: limit number of operating system threads" 中,有過討論,并最終將線程限制數值確定為 10000。

這個值存在的主要目的是限制可以創建無限數量線程的 Go 程序:在程序把操作系統干爆之前,干掉程序。

當然,Go 也暴露了 debug.SetMaxThreads() 方法可以讓我們修改最大線程數值。

  1. package main 
  2.  
  3. import ( 
  4.  "os/exec" 
  5.  "runtime/debug" 
  6.  "time" 
  7.  
  8. func main() { 
  9.  debug.SetMaxThreads(10) 
  10.  for i := 0; i < 20; i++ { 
  11.   go func() { 
  12.    _, err := exec.Command("bash""-c""sleep 3").Output() 
  13.    if err != nil { 
  14.     panic(err) 
  15.    } 
  16.   }() 
  17.  } 
  18.  time.Sleep(time.Second * 5) 

如程序所示,我們將最大線程數設置為 10,然后通過執行 shell 命令 sleep 3 來模擬同步系統調用過程。那么,執行 sleep 操作的 G 和 M 都會阻塞,當程序啟動的線程 M 超過 10 個時,會得到以下報錯。

  1. runtime: program exceeds 10-thread limit 
  2. fatal error: thread exhaustion 
  3. *** 

讓閑置線程退出

閑置線程退出的問題,在官方 issues#14592: "runtime: let idle OS threads exit" 中有過討論。目前,還沒有一個完美的解決方案。

但是,在該 issue 里有人提出使用 runtime.LockOSThread() 方法來殺死線程。

簡單了解下這個函數的特性:

  • 調用 LockOSThread 函數會把當前 G 綁定在當前的系統線程 M 上,這個 G 總是在這個 M 上執行,并且阻止其它 G 在該 M 執行。
  • 只有當前 G 調用了與之前調用 LockOSThread 相同次數的 UnlockOSThread 函數之后,G 與 M 才會解綁。
  • 如果當前 G 在退出時,沒有調用 UnlockOSThread,這個線程會被終止。

那么,我們可以利用第三個特性,在啟動 G 時,調用 LockOSThread 來獨占一個 M。當 G 退出時,而不調用 UnlockOSThread,那這個 M 將不會被閑置,就被終止了。

下面,我們來看一個例子

  1. package main 
  2.  
  3. import ( 
  4.  "fmt" 
  5.  "os/exec" 
  6.  "runtime/pprof" 
  7.  "time" 
  8.  
  9. func main() { 
  10.  threadProfile := pprof.Lookup("threadcreate"
  11.  fmt.Printf(" init threads counts: %d\n", threadProfile.Count()) 
  12.  
  13.  for i := 0; i < 20; i++ { 
  14.   go func() { 
  15.    _, err := exec.Command("bash""-c""sleep 3").Output() 
  16.    if err != nil { 
  17.     panic(err) 
  18.    } 
  19.   }() 
  20.  } 
  21.  time.Sleep(time.Second * 5) 
  22.  fmt.Printf(" end threads counts: %d\n", threadProfile.Count()) 

通過 threadProfile.Count() 我們可以實時獲取當前線程數目,那么在發生了阻塞式系統調用后,該程序的線程數目是多少呢?

  1. init threads counts: 5 
  2. end threads counts: 25 

根據結果可以看到,G 執行完畢后,閑置線程并沒有被釋放。

在程序中添加一行代碼 runtime.LockOSThread() 代碼

  1. go func() { 
  2.  runtime.LockOSThread() // 增加的一行代碼 
  3.  _, err := exec.Command("bash""-c""sleep 3").Output() 
  4.  if err != nil { 
  5.   panic(err) 
  6.  } 
  7. }() 

此時,程序的執行結果如下

  1. init threads counts: 5 
  2.  
  3. end threads counts: 11 

可以看到,由于調用了 LockOSThread 函數的 G 沒有執行 UnlockOSThread 函數,在 G 執行完畢后,M 也被終止了。

總結

在 GMP 模型中,P 與 M 一對一的掛載形式,通過設定 GOMAXPROCS 變量就能控制并行線程數。

當 M 遇到同步系統調用時,G 和 M 會與 P 剝離,當系統調用完成,G 重新進入可運行狀態,而 M 就會被閑置起來。

Go 目前并沒有對閑置線程做清除處理,它們被當作復用的資源,以備后續需要。但是,如果在 Go 程序中積累大量空閑線程,這是對資源的一種浪費,同時對操作系統也產生了威脅。因此,Go 設定了 10000 的默認線程數限制。

我們發現了一種利用 LockOSThread 函數的 trik 做法,可以借此做一些限制線程數的方案:例如啟動定期排查線程數的 goroutine,當發現程序的線程數超過某閾值后,就回收掉一部分閑置線程。

當然,這個方法也存在隱患。例如在 issues#14592 有人提到:當子進程由一個帶有 PdeathSignal: SIGKILL 的 A 線程創建,A 變為空閑時,如果 A 退出,那么子進程將會收到 KILL 信號,從而引起其他問題。

當然,絕大多數情況下,我們的 Go 程序并不會遇到空閑線程數過多的問題。如果真的存在線程數暴漲的問題,那么你應該思考代碼邏輯是否合理(為什么你能允許短時間內如此多的系統同步調用),是否可以做一些例如限流之類的處理。而不是想著通過 SetMaxThreads 方法來處理。

參考

Scheduling In Go:https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html

issues#4056:https://github.com/golang/go/issues/4056

 

issues#14592:https://github.com/golang/go/issues/14592

 

責任編輯:武曉燕 來源: Golang技術分享
相關推薦

2023-10-29 16:14:07

2012-04-23 14:36:10

天璣科技IT成本IT服務

2010-05-31 16:46:31

2011-09-06 10:33:11

2009-01-21 18:31:50

2023-12-29 08:10:41

Go并發開發

2017-12-18 10:27:30

數據中心制冷系統

2014-11-17 10:05:12

Go語言

2023-12-01 08:01:33

GoValidator

2024-04-30 10:29:46

前端開發h5開發函數

2020-07-16 14:25:18

PythonGo前端

2024-02-04 19:38:02

云計算

2022-12-27 09:57:41

線程數CPU

2021-03-29 17:51:00

瑞數信息攻防演練

2021-06-25 15:19:13

攻防演練

2022-06-20 10:45:55

SpringBoot項目

2010-03-16 17:52:27

Java多線程信號量

2009-10-28 10:31:39

在線交易SSL證書

2021-03-29 10:39:07

evSecOps信息安全系統安全

2024-05-08 00:00:00

核心線程數隊列
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产乱码精品一区二区三区av | 久久久看 | 国产综合一区二区 | 久久久91精品国产一区二区精品 | 欧美国产日韩一区 | 日韩欧美国产一区二区三区 | 97精品久久 | 亚洲精品4 | 国产精品久久久久久久久久免费看 | 国产精品久久久久久久久久久免费看 | 国产美女视频一区 | 日日想夜夜操 | 中文字幕第一页在线 | 一区二区在线免费观看 | 99av成人精品国语自产拍 | 精品欧美一区二区久久久伦 | 亚洲精品久久久久久一区二区 | 成人网在线观看 | www.狠狠干 | 欧美一二精品 | 午夜精品久久久久久久久久久久久 | www.久久久久久久久久久久 | 干干干操操操 | 国产99热精品 | 日韩精品无码一区二区三区 | 精品亚洲一区二区三区四区五区高 | 日韩网站免费观看 | 中文字幕一区二区三区四区五区 | 国产精品久久久99 | 亚洲成av人片在线观看 | 涩涩视频网站在线观看 | 成人免费视频网站在线看 | 婷婷丁香在线视频 | 五月婷婷视频 | 一区二区三区免费 | 日韩www| 成人欧美日韩一区二区三区 | 日韩成人在线电影 | 一级毛片视频在线 | 久久国产精品一区 | 美女天堂在线 |