日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go test 如何输出到控制台_深度剖析 Go 中的 Go 协程 (goroutines) -- Go 的并发

發(fā)布時間:2025/3/15 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go test 如何输出到控制台_深度剖析 Go 中的 Go 协程 (goroutines) -- Go 的并发 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
Go 協(xié)程 (goroutine) 是指在后臺中運(yùn)行的輕量級執(zhí)行線程,go 協(xié)程是 Go 中實(shí)現(xiàn)并發(fā)的關(guān)鍵組成部分。

在上次的課程中,我們學(xué)習(xí)了 Go 的并發(fā)模型。由于 Go 協(xié)程相對于傳統(tǒng)操作系統(tǒng)中的線程 (thread) 是非常輕量級的,因此對于一個典型的 Go 應(yīng)用來說,有數(shù)以千計的 Go 協(xié)程并發(fā)運(yùn)行的情形是十分常見的。并發(fā)可以顯著地提升應(yīng)用的運(yùn)行速度,并且可以幫助我們編寫關(guān)注點(diǎn)分離(Separation of concerns,Soc)的代碼。

什么是 Go 協(xié)程?

我們也許在理論上已經(jīng)知曉 Go 協(xié)程是如何工作的,但是在代碼層級上,go 協(xié)程何許物也?其實(shí),go 協(xié)程看起來只是一個與其他眾 Go 協(xié)程并發(fā)運(yùn)行的一個簡單函數(shù)或者方法,但是我們并不能想當(dāng)然地從函數(shù)或者方法中的定義來確定一個 Go 協(xié)程,go 協(xié)程的確定還是要取決于我們?nèi)绾稳フ{(diào)用。

Go 中提供了一個關(guān)鍵字 go 來讓我們創(chuàng)建一個 Go 協(xié)程,當(dāng)我們在函數(shù)或方法的調(diào)用之前添加一個關(guān)鍵字 go,這樣我們就開啟了一個 Go 協(xié)程,該函數(shù)或者方法就會在這個 Go 協(xié)程中運(yùn)行。

舉個簡單的栗子:

https://play.golang.org/p/pIGsToIA2hL

在上面的代碼中,我們定義了一個可以在控制臺輸出 Hello World 字符串的 printHello 的函數(shù),在 main 函數(shù)中,我們就像平時那樣調(diào)用 printHello 函數(shù),最終也是理所當(dāng)然地獲得了期望的結(jié)果。

下面,讓我們嘗試從同一個函數(shù)創(chuàng)建 Go 協(xié)程:

https://play.golang.org/p/LWXAgDpTcJP

根據(jù) Go 協(xié)程的語法,我們在函數(shù)調(diào)用的前面增加了一個 go 關(guān)鍵字,之后程序運(yùn)行正常,輸出了以下的結(jié)果:

main execution started main execution stopped

奇怪的是,Hello World 并沒有如同我們預(yù)料的那樣輸出,這期間究竟發(fā)生了什么?

go 協(xié)程總是在后臺運(yùn)行,當(dāng)一個 Go 協(xié)程執(zhí)行的時候(在這個例子中是 go printHello()), Go 并不會像在之前的那個例子中在執(zhí)行 printHello 中的功能時阻塞 main 函數(shù)中剩下語句的執(zhí)行,而是直接忽略了 Go 協(xié)程的返回并繼續(xù)執(zhí)行 main 函數(shù)剩下的語句。即便如此,我們?yōu)槭裁礇]法看到函數(shù)的輸出呢?

在默認(rèn)情況下,每個獨(dú)立的 Go 應(yīng)用運(yùn)行時就創(chuàng)建了一個 Go 協(xié)程,其 main 函數(shù)就在這個 Go 協(xié)程中運(yùn)行,這個 Go 協(xié)程就被稱為 go 主協(xié)程(main Goroutine,下面簡稱主協(xié)程)。在上面的例子中,主協(xié)程 中又產(chǎn)生了一個 printHello 這個函數(shù)的 Go 協(xié)程,我們暫且叫它 printHello 協(xié)程 吧,因而我們在執(zhí)行上面的程序的時候,就會存在兩個 Go 協(xié)程(main 和 printHello)同時運(yùn)行。正如同以前的程序那樣,go 協(xié)程們會進(jìn)行協(xié)同調(diào)度。因此,當(dāng) 主協(xié)程 運(yùn)行的時候,Go 調(diào)度器在 主協(xié)程 執(zhí)行完之前并不會將控制權(quán)移交給 printHello 協(xié)程。不幸的是,一旦 主協(xié)程 執(zhí)行完畢,整個程序會立即終止,調(diào)度器再也沒有時間留給 printHello 協(xié)程 去運(yùn)行了。

但正如我們從其他課程所知,通過阻塞條件,我們可以手動將控制權(quán)轉(zhuǎn)移給其他的 Go 協(xié)程 , 也可以說是告訴調(diào)度器讓它去調(diào)度其他可用空閑的 Go 協(xié)程。讓我們調(diào)用 time.Sleep() 函數(shù)去實(shí)現(xiàn)它吧。

https://play.golang.org/p/ujQKjpALlRJ

如上圖所示,我們修改了程序,程序在 main 函數(shù)的最后一條語句之前調(diào)用了 time.Sleep(10 * time.Millisecond),使得 主協(xié)程 在執(zhí)行最后一條指令之前調(diào)度器就將控制權(quán)轉(zhuǎn)移給了 printhello 協(xié)程。在這個例子中,我們通過調(diào)用 time.Sleep(10 * time.Millisecond) 強(qiáng)行讓 主協(xié)程 休眠 10ms 并且在在這個 10ms 內(nèi)不會再被調(diào)度器重新調(diào)度運(yùn)行。

一旦 printHello 協(xié)程 執(zhí)行,它就會向控制臺打印‘ Hello World !’,然后該 Go 協(xié)程(printHello 協(xié)程)就會隨之終止,接下來 主協(xié)程 就會被重新調(diào)度(因為 main Go 協(xié)程已經(jīng)睡夠 10ms 了),并執(zhí)行最后一條語句。因此,運(yùn)行上面的程序就會得到以下的輸出 :

main execution started Hello World! main execution stopped

下面我稍微修改一下例子,我在 printHello 函數(shù)的輸出語句之前添加了一條 time.Sleep(time.Millisecond)。我們已經(jīng)知道了如果我們在函數(shù)中調(diào)用了休眠(sleep)函數(shù),這個函數(shù)就會告訴 Go 調(diào)度器去調(diào)度其他可被調(diào)度的 Go 協(xié)程。在上一課中提到,只有非休眠(non-sleeping)的 Go 協(xié)程才會被認(rèn)為是可被調(diào)度的,所以主協(xié)程在這休眠的 10ms 內(nèi)是不會被再次調(diào)度的。因此 主協(xié)程 先打印出“ main execution started ” 接著就創(chuàng)建了一個 printHello 協(xié)程,需要注意此時的 主協(xié)程 還是非休眠狀態(tài)的,在這之后主協(xié)程就要調(diào)用休眠函數(shù)去睡 10ms,并且把這個控制權(quán)讓出來給printHello 協(xié)程。printHello 協(xié)程會先休眠 1ms 告訴調(diào)度器看看有沒有其他可調(diào)度的 Go 協(xié)程,在這個例子里顯然沒有其他可調(diào)度的 Go 協(xié)程了,所以在printHello協(xié)程結(jié)束了這 1ms 的休眠戶就會被調(diào)度器調(diào)度,接著就輸出了“ Hello World ”字符串,之后這個 Go 協(xié)程運(yùn)行結(jié)束。之后,主協(xié)程會在之后的幾毫秒被喚醒,緊接著就會輸出“ main execution stopped ”并且結(jié)束整個程序。

https://play.golang.org/p/rWvzS8UeqD6

上面的程序依舊和之前的例子一樣,輸出以下相同的結(jié)果:

main execution started Hello World! main execution stopped

要是,我把這個printHello 協(xié)程中的休眠 1 毫秒改成休眠 15 毫秒,這個結(jié)果又是如何呢?

https://play.golang.org/p/Pc2nP2BtRiP

在這個例子中,與其他的例子最大的區(qū)別就是printHello 協(xié)程比主協(xié)程的休眠時間還要長,很明顯,主協(xié)程要比 printHello 協(xié)程喚醒要早,這樣的結(jié)果就是主協(xié)程即使喚醒后執(zhí)行完所有的語句,printHello 協(xié)程還是在休眠狀態(tài)。之前提到過,主協(xié)程比較特殊,如果主協(xié)程執(zhí)行結(jié)束后整個程序就要退出,所以 printHello 協(xié)程得不到機(jī)會去執(zhí)行下面的輸出的語句了,所以以上的程序的數(shù)據(jù)結(jié)果如下:

main execution started main execution stopped

使用多 Go 協(xié)程

就像之前我所提到過的,你可以隨心所欲地創(chuàng)建多個 Go 協(xié)程。下面讓我們定義兩個簡單的函數(shù),一個是用于順序打印某個字符串中的每個字符,另一個是順序打印出某個整數(shù)切片中的每個數(shù)字。

https://play.golang.org/p/SJano_g1wTV

在上圖中的程序中,我們連續(xù)地創(chuàng)建了兩個 Go 協(xié)程,程序輸出的結(jié)果如下:

main execution started H e l l o 1 2 3 4 5 main execution stopped

上面的結(jié)果證實(shí)了 Go 協(xié)程是以合作式調(diào)度來運(yùn)作的。下面我們在每個函數(shù)中的輸出語句的下面添加一行 time.Sleep,讓函數(shù)在輸出每個字符或數(shù)字后休息一段時間,好讓調(diào)度器調(diào)度其他可用的 Go 協(xié)程。

https://play.golang.org/p/lrSIEdNxSaH

在上面的程序中,我又修改了一下輸出語句使得我們可以看到每個字符或數(shù)字的輸出時刻。理論上主協(xié)程會休眠 200ms,因此其他 Go 協(xié)程要趕在主協(xié)程喚醒之前做完自己的工作,因為主協(xié)程喚醒之后就會導(dǎo)致程序退出。getChars 協(xié)程每打印一個字符就會休眠 10ms,之后控制權(quán)就會傳給 getDigits 協(xié)程,getDigits 協(xié)程每打印一個數(shù)字后就休眠 30ms,若 getChars 協(xié)程喚醒,則會把控制權(quán)傳回 getChars 協(xié)程,如此往復(fù)。在代碼中可以看到,getChars 協(xié)程會在其他協(xié)程休眠的時候多次進(jìn)行打印字符以及休眠操作,所以我們預(yù)計可以看到輸出的字符比數(shù)字更具有連續(xù)性。

我們在 Windows 上運(yùn)行上面的程序,得到了以下的結(jié)果:

main execution started at time 0s H at time 1.0012ms <-| 1 at time 1.0012ms | almost at the same time e at time 11.0283ms <-| l at time 21.0289ms | ~10ms apart l at time 31.0416ms 2 at time 31.0416ms o at time 42.0336ms 3 at time 61.0461ms <-| 4 at time 91.0647ms | 5 at time 121.0888ms | ~30ms apart main execution stopped at time 200.3137ms | exiting after 200ms

通過以上輸出結(jié)果可以證明我們之前對輸出的討論。對于這個結(jié)果,我們可以通過下面的的程序運(yùn)行圖來解釋。需要注意的是,我們在圖中定義一個輸出語句大約會花費(fèi) 1ms 的 CPU 時間,而這個時間相對于 200ms 來說是可以忽略不計的。

現(xiàn)在我們已經(jīng)知道了如何去創(chuàng)建 Go 協(xié)程以及去如何去使用它。但是使用 time.Sleep 只是一個讓我們獲取理想結(jié)果的一個小技巧。在實(shí)際生產(chǎn)環(huán)境中,我們無法知曉一個 Go 協(xié)程到底需要執(zhí)行多長的時間,因而在 main 函數(shù)里面添加一個 time.Sleep 并不是一個解決問題的方法。我們希望 Go 協(xié)程在執(zhí)行完畢后告知主協(xié)程運(yùn)行的結(jié)果。在目前階段,我們還不知道如何向其他 Go 協(xié)程傳遞以及獲取數(shù)據(jù),簡而言之,就是與其他 Go 協(xié)程進(jìn)行通信。這就是 channels 引入的原因。我們會在下一次課中討論這個東西。

匿名 Go 協(xié)程

如果一個匿名函數(shù)可以退出,那么匿名 Go 協(xié)程也同樣可以退出。請參照functions 課程中的 即時調(diào)用函數(shù)(Immedietly invoked function) 來理解本節(jié)。讓我們修改一下之前 printHello 協(xié)程的例子:

結(jié)果非常明顯,因為我們定義了匿名函數(shù),并在同一語句中作為 Go 協(xié)程執(zhí)行。

需要注意的是,所有的 Go 協(xié)程都是匿名的,因為我們從并發(fā)(concurrency 一課中學(xué)到,go 協(xié)程是不存在標(biāo)識符的,在這里所謂的匿名 Go 協(xié)程只是通過匿名函數(shù)來創(chuàng)建的 Go 協(xié)程罷了


via:深度剖析 Go 中的 Go 協(xié)程 (goroutines) -- Go 的并發(fā)

作者:Uday Hiwarale 譯者:hafrans 校對:polaris1119

本文由 GCTT 原創(chuàng)編譯,Go語言中文網(wǎng) 榮譽(yù)推出

總結(jié)

以上是生活随笔為你收集整理的go test 如何输出到控制台_深度剖析 Go 中的 Go 协程 (goroutines) -- Go 的并发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。