欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go語(yǔ)言更高精度的Sleep實(shí)例解析

 更新時(shí)間:2023年12月20日 10:25:56   作者:晁岳攀(鳥(niǎo)窩)?鳥(niǎo)窩聊技術(shù)  
這篇文章主要為大家介紹了go語(yǔ)言更高精度的Sleep實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

書(shū)接上回,寫(xiě)了一篇《這個(gè)限流庫(kù)兩個(gè)大 bug 存在了半年之久,沒(méi)人發(fā)現(xiàn)?》,提到了 Go 語(yǔ)言中的time.Sleep函數(shù)的問(wèn)題。有網(wǎng)友也私下和我探討,提到這個(gè)可能屬于系統(tǒng)的問(wèn)題,因?yàn)楝F(xiàn)代的操作系統(tǒng)都是分時(shí)操作系統(tǒng),每個(gè)線程可能會(huì)分配一個(gè)或者多個(gè)時(shí)間片,Windows 默認(rèn)線程時(shí)間精度在 15 毫秒,Linux 在 1 毫秒,所以time.Sleep的精度不可能那么高。

嗯,理論上這可以解釋time.Sleep的行為,但是沒(méi)有辦法解釋網(wǎng)友提出的在go 1.16之前的版本中,time.Sleep的精度更高,而go 1.16之后的版本中,time.Sleep的精度更低的問(wèn)題。

time.Sleep精度更低的問(wèn)題

這個(gè)問(wèn)題在 Go 的 bug 系統(tǒng)中有很多,不只是單單上篇文章介紹的#44343, 比如#29485、#61456、#44476、#44608、#61042。這些 bug 中 Ian Lance Taylor 的有些評(píng)論很有價(jià)值,對(duì)于了解 Go 運(yùn)行時(shí)的 Sleep 很有幫助。但是閱覽了這么多的 bug,沒(méi)有人給出為啥go 1.16之后的版本中,time.Sleep的精度更低的解釋?zhuān)降装l(fā)生了啥?或許和 Timer 調(diào)度的變化有關(guān)。

Linux 和 Windows 提供了更高精度的 Sleep, Go 開(kāi)發(fā)者也在嘗試解決 Windows 中過(guò)長(zhǎng)的問(wèn)題。

為了把這個(gè)問(wèn)題說(shuō)明白,我們舉一個(gè)典型的例子,這里我使用了loov/hrtime[1],它能提供更高精度的時(shí)間和 benchmark 方法??吹阶髡叩拿治矣X(jué)得眼熟,果然,作者的一個(gè)項(xiàng)目 lensm 也非常有名。

 intervals := []time.Duration{time.Nanosecond, time.Millisecond, 50 * time.Millisecond}
 for _, interval := range intervals {
  fmt.Printf("sleep %v\n", interval)
  b := hrtime.NewBenchmark(100)
  for b.Next() {
   time.Sleep(interval)
  }
  fmt.Println(b.Histogram(10))
 }

休眠

我們嘗試使用time.Sleep休眠 1 納秒、1 微秒和 50 微秒,可以看到實(shí)際休眠的時(shí)間基本在380ns、1ms50ms。我是在騰訊云上的一臺(tái) Linux 輕量級(jí)服務(wù)器上測(cè)試的,可以看到time.Sleep休眠 1 毫秒以上還是和實(shí)際差不太多的,但是休眠 1 納秒是不太可能的,這也符合我們的預(yù)期,只是實(shí)際休眠的時(shí)間是 380 納秒還是挺長(zhǎng)的。

ubuntu@lab:~/workplace/timer$ go run main.go
sleep 1ns
  avg 726ns;  min 380ns;  p50 476ns;  max 22.4µs;
  p90 670ns;  p99 22.4µs;  p999 22.4µs;  p9999 22.4µs;
      380ns [ 99] ████████████████████████████████████████
        5µs [  0]
       10µs [  0]
       15µs [  0]
       20µs [  1]
       25µs [  0]
       30µs [  0]
       35µs [  0]
       40µs [  0]
       45µs [  0]
sleep 1ms
  avg 1.06ms;  min 1.02ms;  p50 1.06ms;  max 1.09ms;
  p90 1.07ms;  p99 1.09ms;  p999 1.09ms;  p9999 1.09ms;
     1.02ms [  2] █▌
     1.03ms [  6] █████
     1.04ms [  0]
     1.05ms [  1] ▌
     1.06ms [ 48] ████████████████████████████████████████
     1.07ms [ 39] ████████████████████████████████
     1.08ms [  3] ██
     1.09ms [  1] ▌
      1.1ms [  0]
     1.11ms [  0]
sleep 50ms
  avg 50.1ms;  min 50.1ms;  p50 50.1ms;  max 50.1ms;
  p90 50.1ms;  p99 50.1ms;  p999 50.1ms;  p9999 50.1ms;
     50.1ms [  2] ██
     50.1ms [  0]
     50.1ms [  0]
     50.1ms [  1] █
     50.1ms [ 13] ███████████████
     50.1ms [ 34] ████████████████████████████████████████
     50.1ms [ 31] ████████████████████████████████████
     50.2ms [ 15] █████████████████▌
     50.2ms [  2] ██
     50.2ms [  2] ██

其實(shí) Linux 提供了一個(gè)更高精度的系統(tǒng)調(diào)用nanosleep,可以提供納秒級(jí)別的休眠,它是一個(gè)阻塞的系統(tǒng)調(diào)用,會(huì)阻塞當(dāng)前線程,直到睡眠結(jié)束或被中斷。

nanosleep系統(tǒng)調(diào)用和標(biāo)準(zhǔn)庫(kù)的time.Sleep的主要區(qū)別

  • 阻塞方式不同:

    • nanosleep 會(huì)阻塞當(dāng)前線程,直到睡眠結(jié)束或被中斷

    • time.Sleep 會(huì)阻塞當(dāng)前 goroutine

  • 精度不同:

    • nanosleep 可以精確到納秒

    • time.Sleep 最高只能精確到毫秒

  • 中斷處理不同:

    • nanosleep 可以通過(guò)信號(hào)中斷并立即返回

    • time.Sleep 不可以中斷,只能等待睡眠期滿

  • 用途不同:

    • nanosleep 主要用于需要精確睡眠時(shí)間的低級(jí)控制

    • time.Sleep 更適合高級(jí)邏輯控制,不需要精確睡眠時(shí)間

nanosleep替換time.Sleep

我們使用上面的測(cè)試代碼,使用nanosleep替換time.Sleep,看看效果:

 for _, interval := range intervals {
  fmt.Printf("nanosleep %v\n", interval)
  req := syscall.NsecToTimespec(int64(interval))
  b := hrtime.NewBenchmark(100)
  for b.Next() {
   syscall.Nanosleep(&req, nil)
  }
  fmt.Println(b.Histogram(10))
 }

運(yùn)行這段代碼可以得到結(jié)果:

nanosleep 1ns
  avg 60.4µs;  min 58.7µs;  p50 60.2µs;  max 77.5µs;
  p90 61.2µs;  p99 77.5µs;  p999 77.5µs;  p9999 77.5µs;
     58.8µs [ 33] █████████████████████▌
       60µs [ 61] ████████████████████████████████████████
       62µs [  1] ▌
       64µs [  3] █▌
       66µs [  0]
       68µs [  0]
       70µs [  1] ▌
       72µs [  0]
       74µs [  0]
       76µs [  1] ▌

nanosleep 1ms
  avg 1.06ms;  min 1.03ms;  p50 1.06ms;  max 1.07ms;
  p90 1.06ms;  p99 1.07ms;  p999 1.07ms;  p9999 1.07ms;
     1.04ms [  1]
     1.04ms [  0]
     1.05ms [  0]
     1.05ms [  0]
     1.06ms [  0]
     1.06ms [  5] ██
     1.07ms [ 92] ████████████████████████████████████████
     1.07ms [  1]
     1.08ms [  1]
     1.08ms [  0]

nanosleep 50ms
  avg 50ms;  min 50ms;  p50 50ms;  max 50ms;
  p90 50ms;  p99 50ms;  p999 50ms;  p9999 50ms;
     50.1ms [  3] ███▌
     50.1ms [  5] ██████
     50.1ms [ 26] █████████████████████████████████▌
     50.1ms [ 31] ████████████████████████████████████████
     50.1ms [ 18] ███████████████████████
     50.1ms [ 16] ████████████████████▌
     50.1ms [  1] █
     50.1ms [  0]
     50.1ms [  0]
     50.1ms [  0]

可以看到在程序休眠 1 納秒時(shí), nanosleep 實(shí)際休眠 60 納秒,相比于tome.Sleep的 380 納秒,精度提高了很多。但是在休眠 1 毫秒和 50 毫秒時(shí),nanosleep 和 time.Sleep 的精度差不多,都是 1 毫秒和 50 毫秒。

既然 nanosleep 可以提高精度,那么我們能不能以后就使用這個(gè)系統(tǒng)調(diào)用來(lái)代替time.Sleep呢?答案是視情況而定,你需要注意nanosleep是一個(gè)阻塞的系統(tǒng)調(diào)用,Go 程序在調(diào)用它時(shí),會(huì)將當(dāng)前線程阻塞,直到休眠結(jié)束或者被中斷,它會(huì)額外占用一個(gè)線程。如果你的程序中有很多的 goroutine,那么你的程序可能會(huì)因?yàn)樽枞鴮?dǎo)致性能下降。所以你需要權(quán)衡一下,如果你的程序中有很多的 goroutine,而且你的程序中的 goroutine 需要休眠,那么你可以考慮使用time.Sleep,如果你的程序中的 goroutine 不多,而且你的程序中的 goroutine 需要精確的休眠時(shí)間,那么你可以考慮使用nanosleep。

而且,當(dāng)前 Go 并不會(huì)將nanosleep占用的線程主動(dòng)釋放,而且放在池中備用,在并發(fā)nanosleep調(diào)用的時(shí)候,可能會(huì)導(dǎo)致線程數(shù)暴增,下面的代碼演示了這個(gè)情況:

func Threads() {
 var threadProfile = pprof.Lookup("threadcreate")
 fmt.Printf(("threads in starting: %d\n"), threadProfile.Count())
 var sleepTime time.Duration = time.Hour
 req := syscall.NsecToTimespec(int64(sleepTime))
 for i := 0; i < 100; i++ {
  go func() {
   syscall.Nanosleep(&req, nil)
  }()
 }
 time.Sleep(10 * time.Second)
 fmt.Printf(("threads in nanosleep: %d\n"), threadProfile.Count())
}

在我的輕量級(jí)服務(wù)器上,顯示結(jié)果如下:

threads in starting: 4
threads in nanosleep: 103

nanosleep并發(fā)運(yùn)行的時(shí)候,可以看到線程數(shù)達(dá)到了103個(gè)。線程數(shù)暴增會(huì)導(dǎo)致系統(tǒng)資源的浪費(fèi),而且程序性能也會(huì)下降。

當(dāng)然如果你對(duì)threadcreate有疑義,也可以使用pstree查看程序當(dāng)前的線程數(shù)。

線程不會(huì)釋放的問(wèn)題,已經(jīng)在 Go 的 bug 系統(tǒng)中提出了,但是目前還沒(méi)有解決,不過(guò)你可以通過(guò)增加runtime.LockOSThread()這個(gè)技巧來(lái)釋放線程。注意沒(méi)有調(diào)用 UnlockOSThread():

 for i := 0; i &lt; 100; i++ {
  go func() {
   syscall.Nanosleep(&amp;req, nil)
   runtime.LockOSThread()
  }()
 }

本文并沒(méi)有對(duì)生產(chǎn)環(huán)境做任何的建議,只是分析了:

  • time.Sleepnanosleep的精度問(wèn)題

  • nanosleep的使用方法

  • nanosleep的陷阱

算是對(duì)上一篇文章的延伸。

參考資料

[1]

loov/hrtime: https://github.com/loov/hrtime

以上就是go語(yǔ)言更高精度的Sleep的詳細(xì)內(nèi)容,更多關(guān)于go高精度Sleep的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    這篇文章主要為大家介紹了Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 一文帶你了解Go語(yǔ)言中的函數(shù)

    一文帶你了解Go語(yǔ)言中的函數(shù)

    函數(shù)是編程中不可或缺的組成部分,在本文中,我們將詳細(xì)介紹Go語(yǔ)言中函數(shù)的概念和使用方法,包括函數(shù)的定義、參數(shù)和返回值等,需要的可以參考一下
    2023-06-06
  • go語(yǔ)言K8S?的?informer機(jī)制淺析

    go語(yǔ)言K8S?的?informer機(jī)制淺析

    這篇文章為大家主要介紹了go語(yǔ)言K8S?的?informer機(jī)制淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Go語(yǔ)言中實(shí)現(xiàn)完美錯(cuò)誤處理實(shí)踐分享

    Go語(yǔ)言中實(shí)現(xiàn)完美錯(cuò)誤處理實(shí)踐分享

    Go?語(yǔ)言是一門(mén)非常流行的編程語(yǔ)言,由于其高效的并發(fā)編程和出色的網(wǎng)絡(luò)編程能力,越來(lái)越受到廣大開(kāi)發(fā)者的青睞。本文我們就來(lái)深入探討一下Go?語(yǔ)言中的錯(cuò)誤處理機(jī)制吧
    2023-04-04
  • golang中為什么Response.Body需要被關(guān)閉詳解

    golang中為什么Response.Body需要被關(guān)閉詳解

    這篇文章主要給大家介紹了關(guān)于golang中為什么Response.Body需要被關(guān)閉的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • 詳解如何讓Go語(yǔ)言中的反射加快

    詳解如何讓Go語(yǔ)言中的反射加快

    這篇文章主要為大家詳細(xì)介紹了如何讓Go語(yǔ)言中的反射加快的方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定幫助,需要的可以參考一下
    2022-08-08
  • goland服務(wù)熱重啟的配置文件

    goland服務(wù)熱重啟的配置文件

    這篇文章主要介紹了goland服務(wù)熱重啟的配置文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • 一文秒懂Go 編寫(xiě)命令行工具的代碼

    一文秒懂Go 編寫(xiě)命令行工具的代碼

    這篇文章主要介紹了一文秒懂Go 編寫(xiě)命令行工具的代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • 解決panic: assignment to entry in nil map問(wèn)題

    解決panic: assignment to entry in nil

    這篇文章主要介紹了解決panic: assignment to entry in nil map問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2008-01-01
  • golang解析域名的步驟全紀(jì)錄

    golang解析域名的步驟全紀(jì)錄

    這篇文章主要給大家介紹了利用golang如何解析域名的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12

最新評(píng)論