此篇是用來紀錄我理解的 go routine 與相關範例代碼,藉由 ChatGPT 輔助理解和相關解釋,並配合實際嘗試與筆記來理解功能與特性。
Go routine
ChatGPT 解釋:
Go 語言的 goroutine 是一個非常輕量級的執行緒,它可以在一個程序中同時執行多個任務。和傳統的執行緒相比,goroutine 的開銷非常小,可以同時啟動數千甚至數萬個 goroutine,而不會造成系統資源的浪費和效能下降。Go 語言提供了 go 關鍵字來啟動一個新的 goroutine。當遇到 go 關鍵字時,Go 語言會在新的 goroutine 中執行該函數,而不會阻塞主執行緒的運行。這意味著我們可以同時執行多個任務而不需要等待前一個任務完成。
可以啟動多執行緒,也不會阻擋主程序的進程,以我理解的方式就是 Redis Queue 的運作,但一個是 service 一個則是內存功能,可能有些不太一樣的部分但如果是這樣理解會對我會比較好明白。就是執行到時不會阻塞主要流程。
相關範例代碼:
package main
import (
"fmt"
"time"
)
func count(name string) {
for i := 1; i <= 5; i++ {
fmt.Println(name, "count:", i)
time.Sleep(time.Second)
}
}
func main() {
go count("goroutine") // 啟動 goroutine
count("main") // 在主 goroutine 中執行 count
}
// output:
// main count: 1
// goroutine count: 1
// main count: 2
// goroutine count: 2
// main count: 3
// goroutine count: 3
// main count: 4
// goroutine count: 4
// main count: 5
// goroutine count: 5
這邊會看到是交叉的出現計數,為什麼需要放入 time.Sleep(time.Second)
呢?因為如果主程序結束的話 go routine 也會直接中斷運行,所以才會這樣子,另外一個範例:
package main
import (
"fmt"
"time"
)
func longOperation() {
time.Sleep(5 * time.Second) // 模擬耗時操作
fmt.Println("完成耗時操作")
}
func main() {
fmt.Println("啟動goroutine")
go longOperation() // 啟動goroutine
fmt.Println("繼續執行主函式")
time.Sleep(10 * time.Second) // 模擬等待耗時操作
fmt.Println("END")
}
// 啟動goroutine
// 繼續執行主函式
// 完成耗時操作
// END
主程序整個等待時間為:time.Sleep(10 * time.Second)
,而 go routine 裡面則為 Sleep(5 * time.Second)
因此程序可以順利的完成,但如果今天把數值調整一下之後會發生:
time.Sleep(5 * time.Second) // 模擬耗時操作 這邊不調整
time.Sleep(2 * time.Second) // 模擬等待耗時操作 這邊改為<5
// 啟動goroutine
// 繼續執行主函式
// END
這邊可以看到 go routine 的 print 沒被執行到,或是執行到一半被中斷,原因是因為主程序已經執行完強迫結束應用,ChatGPT 資料如下:
在 Go 中,主程序(main)和 goroutine 之間的執行存在幾個注意事項:
- 主程序的執行不會等待 goroutine 完成,除非明確地使用同步原語(如通道)來等待 goroutine 的結果。
- 當主程序結束時,所有還在運行的 goroutine 也會被停止。
- 在 goroutine 中發生的 panic 不會影響其他 goroutine 的執行,也不會影響主程序的執行。
- goroutine 需要佔用一定的資源(如線程、內存等),如果過度使用 goroutine,可能會導致系統資源不足,進而導致程序崩潰。
因此,在使用 goroutine 時,需要注意以下幾點:
- 在啟動 goroutine 前,要確保所有的變量都被初始化或者設置好。
- 在啟動 goroutine 前,要確保所有的依賴都被解決或者明確地處理好。
- 在 goroutine 中,需要避免競爭條件和死鎖問題。可以使用通道、互斥鎖等同步原語來解決這些問題。
- 如果啟動了大量的 goroutine,需要注意控制其數量,避免過度佔用系統資源。
Channel
ChatGPT 解釋: 在 Go 中,channel 是一種用於在 goroutine 之間進行通信的原語。channel 可以看作是一個管道,goroutine 可以通過管道進行數據的傳輸和同步,以實現並發執行。
以下是 Go channel 的一些特性:
- 創建 channel:可以使用內建的 make 函數來創建 channel。例如,make(chan int) 創建了一個傳遞整數的 channel。
- 發送和接收數據:可以使用 <- 運算符來發送和接收數據。例如,ch <- 42 發送數字 42 到 channel,x := <-ch 從 channel 中接收一個數字。
- 非阻塞操作:如果 channel 中沒有數據或者已滿,發送操作和接收操作會被阻塞。可以使用 default 分支來實現非阻塞的發送和接收操作。
- 单向 channel:可以使用 chan<- 和 <-chan 來定義發送和接收操作的 channel。例如,ch1 := make(chan<- int) 定義了一個只能發送數字的 channel。
- 關閉 channel:可以使用 close 函数來關閉 channel。關閉後,接收操作會繼續從 channel 中讀取數據,直到 channel 中的所有數據都被讀取完畢。 總之,Go channel 是一種用於實現 goroutine 之間通信和同步的重要原語。通過使用 channel,可以實現並發執行,避免競爭條件和死鎖問題,提高程序的可靠性和穩定性。
這邊以一個不錯的範例作為理解,結合 go routine 機制,用於併發檢查網站服務狀態:
package main
import (
"fmt"
"net/http"
)
func crawl(url string, ch chan<- string) {
resp, err := http.Get(url) // 發送http請求
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
ch <- fmt.Sprintf("URL: %s, Status: %s", url, resp.Status) // 將數據傳遞到channel中
}
func main() {
urls := []string{
"https://www.google.com",
"https://www.facebook.com",
"https://www.github.com",
}
ch := make(chan string) // 創建一個string型的channel
for _, url := range urls {
go crawl(url, ch) // 同時啟動多個goroutine,從不同的網站上收集數據
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-ch) // 從channel中讀取數據並輸出
}
}
// URL: https://www.google.com, Status: 200 OK
// URL: https://www.facebook.com, Status: 200 OK
// URL: https://www.github.com, Status: 200 OK
我這邊的理解是 Channel(in-memory) 對標的是我理解的 Redis(database) 機制,並且是 FIFO 機制(Queue)。也可以將 channel 當成一種 flag 等待操作,也可以透過多 channel 來實現相互等待的機制:
package main
import (
"fmt"
"time"
)
func doWork(flag chan bool) {
for {
select {
case <-flag:
fmt.Println("Flag is set. Stopping the work.")
return
default:
fmt.Println("Doing some work...")
time.Sleep(time.Second)
}
}
}
func main() {
flag := make(chan bool)
go doWork(flag)
time.Sleep(5 * time.Second)
fmt.Println("Setting flag to stop the work...")
flag <- true
time.Sleep(2 * time.Second)
fmt.Println("Exiting...")
}
// Doing some work...
// Doing some work...
// Doing some work...
// Doing some work...
// Doing some work...
// Setting flag to stop the work...
// Flag is set. Stopping the work.
// Exiting...