概述
在上一節(jié)的內(nèi)容中毒费,我們介紹了Go的類型轉(zhuǎn)換,包括:斷言類型轉(zhuǎn)換愈魏、顯式類型轉(zhuǎn)換觅玻、隱式類型轉(zhuǎn)換、強制類型轉(zhuǎn)換培漏、strconv包等溪厘。在本節(jié)中,我們將介紹Go的并發(fā)牌柄。Go語言以其強大的并發(fā)模型而聞名畸悬,其并發(fā)特性主要通過以下幾個元素來實現(xiàn):Goroutines、Channels珊佣、WaitGroups蹋宦、Mutex和Select披粟。通過結(jié)合使用以上元素,Go語言提供了強大的并發(fā)支持冷冗,使得編寫高效守屉、高性能、高吞吐量的并發(fā)程序變得相對容易蒿辙。
Goroutines
Goroutines是Go語言中輕量級的并發(fā)單元拇泛,可以與其他goroutine并發(fā)執(zhí)行。它們在相同的地址空間內(nèi)運行思灌,但每個goroutine都有自己的棧和局部變量俺叭。Goroutine的啟動和銷毀開銷很小,使得在程序中可以創(chuàng)建大量的Goroutine习瑰。相比于線程绪颖,Goroutine的創(chuàng)建和管理成本更低,因為它們不需要像線程一樣分配固定的內(nèi)存空間甜奄。此外柠横,Goroutine之間可以通過Channels進行通信,避免使用共享內(nèi)存和信號量等機制课兄,從而避免了競態(tài)條件和數(shù)據(jù)競爭等問題牍氛。
Goroutine是Go語言的主要并發(fā)原語,通常用于實現(xiàn)高并發(fā)的應(yīng)用程序烟阐。Go運行時將Goroutine有效地調(diào)度到真實的線程上搬俊,以避免浪費資源。因此蜒茄,可以輕松地創(chuàng)建大量的Goroutine(比如:每個請求一個Goroutine)唉擂,并且可以編寫簡單的、命令式的阻塞代碼檀葛。
Goroutine的語法格式為:
go <func_name>(<arguments>)
其中玩祟,go關(guān)鍵字表示啟動一個新的Goroutine,func_name表示要啟動的函數(shù)名屿聋,arguments表示傳遞給函數(shù)的參數(shù)列表空扎。通過在函數(shù)調(diào)用前加上go關(guān)鍵字,可以啟動一個新的Goroutine來執(zhí)行該函數(shù)润讥。這個Goroutine將與其他Goroutine并發(fā)執(zhí)行转锈,并且不需要顯式地創(chuàng)建和管理線程。
在下面的示例代碼中楚殿,我們使用go關(guān)鍵字啟動了兩個Goroutine來執(zhí)行printMsg函數(shù)撮慨。每個Goroutine都會打印出相應(yīng)的消息,并且通過time.Sleep函數(shù)來模擬一些耗時的操作。主Goroutine在啟動了其他兩個Goroutine之后會等待一段時間甫煞,以確保所有Goroutine都有足夠的時間來執(zhí)行完畢菇曲。
package main
import (
"fmt"
"time"
)
func main() {
// 啟動第1個Goroutine
go printMsg("Hello")
// 啟動第2個Goroutine
go printMsg("World")
// 等待一段時間,以確保所有Goroutine執(zhí)行完畢
time.Sleep(time.Second)
}
func printMsg(msg string) {
for i := 0; i < 5; i++ {
fmt.Println(msg)
// 模擬耗時的操作
time.Sleep(200 * time.Millisecond)
}
}
注意:Goroutine之間的執(zhí)行順序是不確定的抚吠,因此每次運行程序都會得到不同的輸出結(jié)果常潮,這取決于Go運行時調(diào)度器的實現(xiàn)細節(jié)和系統(tǒng)負載等因素。
Channels
Channels是一種通信機制楷力,用于在goroutines之間進行數(shù)據(jù)傳輸和同步操作喊式。Channels支持發(fā)送和接收操作,并且可以在發(fā)送和接收操作之間進行阻塞萧朝,以實現(xiàn)同步岔留。Channels的使用非常靈活,可以根據(jù)需要進行單向或雙向數(shù)據(jù)傳輸检柬。它們可以用于在不同的goroutines之間傳遞數(shù)據(jù)献联,以及實現(xiàn)數(shù)據(jù)共享。
在創(chuàng)建Channels時何址,可以指定其緩沖區(qū)大小里逆,緩沖區(qū)的大小決定了可以存儲在Channels中的數(shù)據(jù)量。如果空閑緩沖區(qū)為空用爪,發(fā)送操作會被阻塞原押,直到有接收操作。如果空閑緩沖區(qū)已滿偎血,接收操作會被阻塞诸衔,直到有發(fā)送操作。這種機制可以實現(xiàn)數(shù)據(jù)在goroutines之間的有效傳輸和同步颇玷。
注意:不同類型的Channel有不同的性能和用途笨农。無緩沖的Channel(即緩沖區(qū)大小為0)可以在發(fā)送和接收操作之間進行同步,而有緩沖的Channel可以提高并發(fā)性能帖渠,但需要小心處理緩沖區(qū)溢出的問題谒亦。
Channel的語法格式為:
chan <type>
其中,type表示Channel中傳輸?shù)臄?shù)據(jù)類型阿弃。比如:chan int表示一個用于傳輸整數(shù)類型的Channel。除了指定數(shù)據(jù)類型之外羞延,還可以使用chan來創(chuàng)建具有不同緩沖區(qū)大小的Channel渣淳。比如:chan int buffer(10)表示創(chuàng)建一個緩沖區(qū)大小為10的整數(shù)類型Channel。
除了使用chan來創(chuàng)建Channel之外伴箩,還可以使用內(nèi)置的make函數(shù)來創(chuàng)建具有指定類型的Channel入愧。比如:make(chan int)表示創(chuàng)建一個整數(shù)類型的無緩沖Channel。
在使用Channel時,可以使用以下操作進行數(shù)據(jù)傳輸和同步棺蛛。
x := <-ch:從Channel中接收數(shù)據(jù)怔蚌,并將接收到的數(shù)據(jù)賦值給變量x。
ch <- x:向Channel中發(fā)送數(shù)據(jù)旁赊,并將變量x的值發(fā)送到Channel中桦踊。
如果Channel被阻塞,則接收操作將阻塞直到有數(shù)據(jù)可用终畅。如果發(fā)送操作導(dǎo)致緩沖區(qū)已滿籍胯,則發(fā)送操作將阻塞直到有空間可用。
在下面的示例代碼中离福,我們將數(shù)組分為兩個切片杖狼,并通過兩個goroutine來計算切片之和。在goroutine完成計算并將切片之和發(fā)送到通道后妖爷,main函數(shù)會從通道中接收數(shù)據(jù)蝶涩,并計算最終的總和。
package main
import "fmt"
func sum(s []int, c chan int) {
total := 0
for _, v := range s {
total += v
}
// 把total發(fā)送到通道
c <- total
}
func main() {
data := []int{1, 2, 3, 4, 5, 6}
c := make(chan int)
offset := len(data) / 2
go sum(data[:offset], c)
go sum(data[offset:], c)
// 從通道中接收結(jié)果
x, y := <-c, <-c
// 輸出:15 6 21 或 6 15 21
fmt.Println(x, y, x + y)
}
除了逐個接收數(shù)據(jù)之外絮识,還可以通過range關(guān)鍵字來遍歷讀取到的數(shù)據(jù)绿聘。注意:使用range遍歷時,需要確保發(fā)送完數(shù)據(jù)后笋除,及時調(diào)用close()函數(shù)來關(guān)閉通道斜友。否則,range遍歷不會結(jié)束垃它,會一直阻塞等待接收新的數(shù)據(jù)鲜屏。
在下面的示例代碼中,我們首先使用make函數(shù)創(chuàng)建了一個整數(shù)類型的Channel国拇。然后洛史,我們啟動一個匿名的Goroutine來循環(huán)發(fā)送數(shù)字10至50到Channel中,并在發(fā)送完畢后關(guān)閉Channel酱吝。最后也殖,我們在主Goroutine中使用range關(guān)鍵字來迭代接收Channel中的數(shù)據(jù),并將其打印輸出务热。
通過調(diào)用close函數(shù)可關(guān)閉一個Channel忆嗜,關(guān)閉Channel表示再也不會向該Channel發(fā)送任何數(shù)據(jù)。對于已經(jīng)發(fā)送到Channel中的數(shù)據(jù)崎岂,仍然可以被接收捆毫。由于Channel已經(jīng)被關(guān)閉,迭代接收數(shù)據(jù)將自動停止冲甘。
package main
import "fmt"
func main() {
// 創(chuàng)建一個整數(shù)類型的Channel
ch := make(chan int)
// 啟動一個Goroutine
go func() {
for i := 10; i <= 50; i += 10 {
// 發(fā)送數(shù)據(jù)到Channel
ch <- i
fmt.Println("sub routine:", i)
}
// 關(guān)閉Channel
close(ch)
}()
// 從Channel接收數(shù)據(jù)绩卤,依次輸出:10 20 30 40 50
for num := range ch {
fmt.Println(num)
}
}
WaitGroups
在Go語言中途样,WaitGroups是sync包中的一個類型,用于等待一組Goroutine執(zhí)行完成濒憋。它提供了一種方便的方式何暇,以確保所有的Goroutine都執(zhí)行完畢后,再繼續(xù)執(zhí)行后續(xù)的邏輯凛驮。
WaitGroups的使用比較簡單:首先裆站,需要創(chuàng)建一個WaitGroups實例;然后辐烂,通過調(diào)用Add()函數(shù)增加等待的Goroutine數(shù)量遏插,每個Goroutine執(zhí)行完畢后要調(diào)用Done()函數(shù)進行計數(shù)減一;最后纠修,在主Goroutine中調(diào)用Wait()函數(shù)來等待所有的Goroutine都執(zhí)行完畢胳嘲。
在下面的示例代碼中,我們創(chuàng)建了一個WaitGroups實例wg扣草,然后通過調(diào)用Add()函數(shù)增加了兩個Goroutine了牛。每個Goroutine中使用defer語句調(diào)用Done()函數(shù)來標(biāo)記該Goroutine的執(zhí)行完成。最后辰妙,在主Goroutine中調(diào)用Wait()函數(shù)來等待所有的Goroutine都執(zhí)行完畢鹰祸,然后繼續(xù)執(zhí)行后續(xù)的邏輯。
package main
import "fmt"
import "sync"
import "time"
func main() {
var wg sync.WaitGroup
// 啟動第一個Goroutine
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Goroutine 1 started")
time.Sleep(1 * time.Second)
fmt.Println("Goroutine 1 finished")
}()
// 啟動第二個Goroutine
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Goroutine 2 started")
time.Sleep(2 * time.Second)
fmt.Println("Goroutine 2 finished")
}()
// 等待所有Goroutine執(zhí)行完畢
wg.Wait()
// 所有Goroutine執(zhí)行完畢后密浑,繼續(xù)執(zhí)行后續(xù)邏輯
fmt.Println("All Goroutines finished")
}
Mutex
在Go語言中蛙婴,mutex是一種用于實現(xiàn)并發(fā)安全的鎖機制。它提供了一種簡單的方式來保護共享資源尔破,以避免多個Goroutine同時訪問和修改數(shù)據(jù)街图,從而導(dǎo)致競爭條件或數(shù)據(jù)不一致的問題。mutex通常是通過sync.Mutex類型來實現(xiàn)的懒构,這個類型提供了兩個函數(shù):Lock和Unlock餐济。
在下面的示例代碼中,我們定義了一個全局變量counter和一個sync.Mutex類型的變量mutex胆剧。在increment函數(shù)中絮姆,我們使用mutex.Lock()來鎖定mutex,以確保在同一時間只有一個Goroutine可以訪問和修改counter秩霍。在完成對counter的修改后篙悯,使用defer mutex.Unlock()來解鎖mutex,以確保在函數(shù)返回之前釋放鎖铃绒,從而允許其他Goroutine獲取鎖并訪問共享資源鸽照。最后,在主函數(shù)中匿垄,我們啟動了5個并發(fā)的Goroutine來增加計數(shù)器的值移宅,并等待一段時間后打印最終的計數(shù)結(jié)果。
package main
import "fmt"
import "sync"
import "time"
var (
counter int
mutex sync.Mutex
)
func increment() {
// 鎖定mutex椿疗,確保同一時間只有一個Goroutine可以訪問和修改counter
mutex.Lock()
defer mutex.Unlock()
// 增加計數(shù)器的值
fmt.Println("Current counter:", counter)
counter++
}
func main() {
// 啟動5個并發(fā)的Goroutine來增加計數(shù)器的值
for i := 0; i < 5; i++ {
go increment()
}
// 等待所有Goroutine執(zhí)行完畢
time.Sleep(time.Second)
fmt.Println("Final counter:", counter)
}
Select
select語句用于在多個通道操作之間進行選擇漏峰,它允許你等待多個通道操作中的任意一個完成,然后執(zhí)行對應(yīng)的代碼塊届榄。其語法如下:
select {
case <-channel1:
// 執(zhí)行channel1操作完成的代碼塊
case <-channel2:
// 執(zhí)行channel2操作完成的代碼塊
case <-channel3:
// 執(zhí)行channel3操作完成的代碼塊
default:
// 如果沒有任何通道操作完成浅乔,執(zhí)行default代碼塊
}
在select語句中,每個case子句必須是一個通道操作铝条。當(dāng)其中一個通道操作完成時靖苇,對應(yīng)的代碼塊將被執(zhí)行。如果沒有任何通道操作完成班缰,且存在default子句贤壁,則執(zhí)行default代碼塊。
在下面的示例代碼中埠忘,我們創(chuàng)建了三個通道脾拆,并使用三個Goroutine分別向這三個通道發(fā)送消息。然后莹妒,在select語句中等待哪個通道先完成操作名船,并打印收到的消息。由于發(fā)送消息的Goroutine使用了不同的延遲時間旨怠,因此最終打印的消息取決于哪個通道最先完成操作渠驼。
package main
import "fmt"
import "time"
func func1(channel1 chan string) {
time.Sleep(1 * time.Second)
channel1 <- "Channel 1"
}
func func2(channel2 chan string) {
time.Sleep(2 * time.Second)
channel2 <- "Channel 2"
}
func func3(channel3 chan string) {
time.Sleep(3 * time.Second)
channel3 <- "Channel 3"
}
func main() {
channel1 := make(chan string)
channel2 := make(chan string)
channel3 := make(chan string)
go func1(channel1)
go func2(channel2)
go func3(channel3)
select {
case msg1 := <-channel1:
fmt.Println("Received from Channel 1:", msg1)
case msg2 := <-channel2:
fmt.Println("Received from Channel 2:", msg2)
case msg3 := <-channel3:
fmt.Println("Received from Channel 3:", msg3)
}
}