最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何有效控制Go線程數(shù)實(shí)例探究

 更新時間:2024年01月18日 10:26:36   作者:機(jī)器鈴砍菜刀  
這篇文章主要為大家介紹了如何有效控制?Go?線程數(shù)的問題探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

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

這是對的,因?yàn)?Go 對運(yùn)行時創(chuàng)建的線程數(shù)量有一個限制,默認(rèn)是 10000 個線程。今天我們就來探討一下 Go 線程數(shù)相關(guān)的問題。

閑置線程

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

GMP 模型

我們可以通過設(shè)置 GOMAXPROCS 來設(shè)定 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 的定義文檔,我們可以看到該變量只是限制了可以同時執(zhí)行用戶級 Go 代碼的 OS 系統(tǒng)線程數(shù)量(通俗地講:Go 程序最多只能有和 P 相等個數(shù)的系統(tǒng)線程同時運(yùn)行)。但是,在系統(tǒng)調(diào)用中被阻塞的線程不在此限制之中。

對于系統(tǒng)調(diào)用,可分為同步和異步兩種方式。

我們在《Go 網(wǎng)絡(luò)編程和 TCP 抓包實(shí)操》一文中闡述的 Go 網(wǎng)絡(luò)編程模型,就是一種異步系統(tǒng)調(diào)用。它使用網(wǎng)路輪詢器進(jìn)行系統(tǒng)調(diào)用,調(diào)度器可以防止 G 在進(jìn)行這些系統(tǒng)調(diào)用時阻塞 M。這可以讓 M 繼續(xù)執(zhí)行其他的 G,而不需要創(chuàng)建新的 M。

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

同步系統(tǒng)調(diào)用 1

如上圖所示,運(yùn)行在 M1 上的 G1 想要請求一個同步系統(tǒng)調(diào)用。

同步系統(tǒng)調(diào)用 2

當(dāng)發(fā)生同步系統(tǒng)調(diào)用并阻塞時,調(diào)度器將 M1 和仍然掛載在其之上的 G1 與 P 分離,并引入新的 M2 來運(yùn)行 P 上的其他 G。

同步系統(tǒng)調(diào)用 3

當(dāng) G1 進(jìn)行的阻塞式系統(tǒng)調(diào)用結(jié)束時,G1 重新回到 P 的 LRQ 中去,但 M1 變成了閑置線程,不會被回收,以留備后續(xù)復(fù)用。

問題來了,如果在某一短時段內(nèi),Go 程序存在大量無法短暫結(jié)束的同步系統(tǒng)調(diào)用,那線程數(shù)豈不是會一直漲下去?

最大線程數(shù)限制

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

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

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

package main

import (
 "os/exec"
 "runtime/debug"
 "time"
)

func main() {
 debug.SetMaxThreads(10)
 for i := 0; i < 20; i++ {
  go func() {
   _, err := exec.Command("bash", "-c", "sleep 3").Output()
   if err != nil {
    panic(err)
   }
  }()
 }
 time.Sleep(time.Second * 5)
}

如程序所示,我們將最大線程數(shù)設(shè)置為 10,然后通過執(zhí)行 shell 命令 sleep 3 來模擬同步系統(tǒng)調(diào)用過程。那么,執(zhí)行 sleep 操作的 G 和 M 都會阻塞,當(dāng)程序啟動的線程 M 超過 10 個時,會得到以下報(bào)錯。

runtime: program exceeds 10-thread limit
fatal error: thread exhaustion
***

讓閑置線程退出

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

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

簡單了解下這個函數(shù)的特性:

  • 調(diào)用 LockOSThread 函數(shù)會把當(dāng)前 G 綁定在當(dāng)前的系統(tǒng)線程 M 上,這個 G 總是在這個 M 上執(zhí)行,并且阻止其它 G 在該 M 執(zhí)行。

  • 只有當(dāng)前 G 調(diào)用了與之前調(diào)用 LockOSThread 相同次數(shù)的 UnlockOSThread 函數(shù)之后,G 與 M 才會解綁。

  • 如果當(dāng)前 G 在退出時,沒有調(diào)用 UnlockOSThread,這個線程會被終止。

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

下面,我們來看一個例子

package main

import (
 "fmt"
 "os/exec"
 "runtime/pprof"
 "time"
)

func main() {
 threadProfile := pprof.Lookup("threadcreate")
 fmt.Printf(" init threads counts: %d\n", threadProfile.Count())

 for i := 0; i < 20; i++ {
  go func() {
   _, err := exec.Command("bash", "-c", "sleep 3").Output()
   if err != nil {
    panic(err)
   }
  }()
 }
 time.Sleep(time.Second * 5)
 fmt.Printf(" end threads counts: %d\n", threadProfile.Count())
}

通過 threadProfile.Count() 我們可以實(shí)時獲取當(dāng)前線程數(shù)目,那么在發(fā)生了阻塞式系統(tǒng)調(diào)用后,該程序的線程數(shù)目是多少呢?

 init threads counts: 5
 end threads counts: 25

根據(jù)結(jié)果可以看到,G 執(zhí)行完畢后,閑置線程并沒有被釋放。

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

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

此時,程序的執(zhí)行結(jié)果如下

 init threads counts: 5
 end threads counts: 11

可以看到,由于調(diào)用了 LockOSThread 函數(shù)的 G 沒有執(zhí)行 UnlockOSThread 函數(shù),在 G 執(zhí)行完畢后,M 也被終止了。

總結(jié)

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

當(dāng) M 遇到同步系統(tǒng)調(diào)用時,G 和 M 會與 P 剝離,當(dāng)系統(tǒng)調(diào)用完成,G 重新進(jìn)入可運(yùn)行狀態(tài),而 M 就會被閑置起來。

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

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

當(dāng)然,這個方法也存在隱患。例如在 issues#14592 有人提到:當(dāng)子進(jìn)程由一個帶有 PdeathSignal: SIGKILL 的 A 線程創(chuàng)建,A 變?yōu)榭臻e時,如果 A 退出,那么子進(jìn)程將會收到 KILL 信號,從而引起其他問題。

當(dāng)然,絕大多數(shù)情況下,我們的 Go 程序并不會遇到空閑線程數(shù)過多的問題。如果真的存在線程數(shù)暴漲的問題,那么你應(yīng)該思考代碼邏輯是否合理(為什么你能允許短時間內(nèi)如此多的系統(tǒng)同步調(diào)用),是否可以做一些例如限流之類的處理。而不是想著通過 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 

以上就是如何有效控制 Go 線程數(shù)的詳細(xì)內(nèi)容,更多關(guān)于Go線程數(shù)控制 的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go類型安全的HTTP請求示例詳解

    Go類型安全的HTTP請求示例詳解

    這篇文章主要為大家介紹了Go類型安全的HTTP請求示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型

    Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型

    這篇文章主要介紹了Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型,根據(jù)Go泛型使用的三步曲提到的:類型參數(shù)化、定義類型約束、類型實(shí)例化我們一步步來定義我們的緩存結(jié)構(gòu)體,需要的朋友可以參考下
    2022-07-07
  • Ubuntu18.04 LTS搭建GO語言開發(fā)環(huán)境過程解析

    Ubuntu18.04 LTS搭建GO語言開發(fā)環(huán)境過程解析

    這篇文章主要介紹了Ubuntu18.04 LTS搭建GO語言開發(fā)環(huán)境過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-11-11
  • Go?Java算法之單詞搜索示例詳解

    Go?Java算法之單詞搜索示例詳解

    這篇文章主要為大家介紹了Go?Java算法之單詞搜索示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go語言實(shí)現(xiàn)簡單Web服務(wù)器的方法

    Go語言實(shí)現(xiàn)簡單Web服務(wù)器的方法

    這篇文章主要介紹了Go語言實(shí)現(xiàn)簡單Web服務(wù)器的方法,實(shí)例分析了Web服務(wù)器的實(shí)現(xiàn)原理與相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • Golang 刪除文件并遞歸刪除空目錄的操作

    Golang 刪除文件并遞歸刪除空目錄的操作

    這篇文章主要介紹了Golang 刪除文件并遞歸刪除空目錄的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • go?smtp實(shí)現(xiàn)郵件發(fā)送示例詳解

    go?smtp實(shí)現(xiàn)郵件發(fā)送示例詳解

    這篇文章主要為大家介紹了go?smtp實(shí)現(xiàn)郵件發(fā)送示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Go中sync.Once源碼的深度講解

    Go中sync.Once源碼的深度講解

    sync.Once是Go語言標(biāo)準(zhǔn)庫中的一個同步原語,用于確保某個操作只執(zhí)行一次,本文將從源碼出發(fā)為大家詳細(xì)介紹一下sync.Once的具體使用,x希望對大家有所幫助
    2025-01-01
  • Go逃逸分析示例詳解

    Go逃逸分析示例詳解

    這篇文章主要為大家介紹了Go逃逸分析示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 基于微服務(wù)框架go-micro開發(fā)gRPC應(yīng)用程序

    基于微服務(wù)框架go-micro開發(fā)gRPC應(yīng)用程序

    這篇文章介紹了基于微服務(wù)框架go-micro開發(fā)gRPC應(yīng)用程序的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07

最新評論

河西区| 台东县| 铜陵市| 威远县| 阿拉尔市| 临邑县| 万年县| 鄂伦春自治旗| 大邑县| 崇明县| 咸宁市| 武宁县| 贵南县| 高平市| 北宁市| 东至县| 东山县| 永兴县| 汪清县| 松滋市| 肇源县| 绥滨县| 吉木萨尔县| 栾川县| 双鸭山市| 鄂托克旗| 五台县| 贡嘎县| 会昌县| 海门市| 旺苍县| 磐安县| 湾仔区| 定襄县| 崇州市| 朝阳市| 武穴市| 姚安县| 忻州市| 绥棱县| 瑞金市|