當我學javascript的時候,我非常喜歡一個觀念,那就是非同步,我覺得這個觀念非常的方便,他可以讓使用者可以繼續互動,而不會因為某個操作而感到網頁"凍結",更可以更容易地進行錯誤處理
生為現在多核時代,我們大多希望透過多線程的方式來處理程式,已達到高效率...疑,先等等,有些觀念我們可能要說的清楚一點,像是...
併發 (Concurrency)
並行 (Parallelism)
所以,併發可以在單核或多核系統上發生,而並行則特指在多核系統上的真正同時執行的任務。
在傳統的程式語言C++和Java最初被設計出來時,它們主要是為了解決一般的編程問題,而不是專門為併發編程設計的,雖然C++和Java都可以使用多線程(即同時執行多個任務),但它們的多線程功能主要是依賴於操作系統提供的,當你在C++或Java中創建一個新的線程時,實際上是操作系統在背後幫你創建和管理這個線程。這意味著線程的行為和性能受到操作系統的影響
Go 語言的設計哲學之一就是原生併發輕量高效, 並且在處理併發時,並不直接依賴於操作系統提供的線程,因為Go引入了 Goroutines 作為其併發模型的核心部分
那Goroutines跟操作系統的線程 是什麼關係呢
下面我們用服務生跟廚師來比喻
服務生 (Goroutines):
廚師 (OS Threads):
併發:
這種方式允許 Go 語言有效地管理大量的 Goroutines,並確保它們都能被 OS Threads 正確地處理,從而實現高效的併發處理
接下來我們再來看這操作系統的線程及Goroutines的比較
操作系統的線程 (OS Threads):
Go 的 Goroutines:
Go 的調度器:
以下是 Goroutines及OS Threads的關係圖
如果是java呢
從上面的對照表可以明顯地感受到,Go就是一個面向併發而生的語言了,因此在應用結構的設計上 GO的慣例就是優先考慮併發設計
你在一家餐廳,這家餐廳有一個(非併發)或數個(併發)服務生和一個廚師
package main
import (
"fmt"
"sync"
"time"
)
// 廚師的功能
func chef(order int) {
fmt.Printf("廚師開始製作訂單 #%d\n", order)
time.Sleep(2 * time.Second) // 廚師製作食物需要2秒
fmt.Printf("廚師完成訂單 #%d\n", order)
}
// 服務生的功能
func waiter(order int, wg *sync.WaitGroup) {
fmt.Printf("服務生接收到訂單 #%d\n", order)
chef(order) // 服務生將訂單交給廚師
if wg != nil {
wg.Done()
}
}
如果是併發版本的話
func main() {
var wg sync.WaitGroup
// 三位顧客同時下訂單
wg.Add(3)
go waiter(1, &wg)
go waiter(2, &wg)
go waiter(3, &wg)
wg.Wait()
fmt.Println("所有訂單都已完成!")
}
執行後,輸出為
❯ go run main.go
服務生接收到訂單 #1
廚師開始製作訂單 #1
服務生接收到訂單 #3
廚師開始製作訂單 #3
服務生接收到訂單 #2
廚師開始製作訂單 #2
廚師完成訂單 #2
廚師完成訂單 #1
廚師完成訂單 #3
所有訂單都已完成!
非併發版本:
func main() {
// 三位顧客依次下訂單
waiter(1, nil)
waiter(2, nil)
waiter(3, nil)
fmt.Println("所有訂單都已完成!")
}
執行後,輸出為
❯ go run main.go
服務生接收到訂單 #1
廚師開始製作訂單 #1
廚師完成訂單 #1
服務生接收到訂單 #2
廚師開始製作訂單 #2
廚師完成訂單 #2
服務生接收到訂單 #3
廚師開始製作訂單 #3
廚師完成訂單 #3
所有訂單都已完成!
不知道你有沒有感受到併發的威力,希望以上的解說可以讓你能理解