Go to Learn Go之并發(fā)

概述

在上一節(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)
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鉴腻,隨后出現(xiàn)的幾起案子迷扇,更是在濱河造成了極大的恐慌,老刑警劉巖拘哨,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋梭,死亡現(xiàn)場離奇詭異,居然都是意外死亡倦青,警方通過查閱死者的電腦和手機瓮床,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來产镐,“玉大人隘庄,你說我怎么就攤上這事⊙⒀牵” “怎么了丑掺?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長述雾。 經(jīng)常有香客問我街州,道長兼丰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任唆缴,我火速辦了婚禮鳍征,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘面徽。我一直安慰自己艳丛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布趟紊。 她就那樣靜靜地躺著氮双,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霎匈。 梳的紋絲不亂的頭發(fā)上戴差,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音铛嘱,去河邊找鬼造挽。 笑死,一個胖子當(dāng)著我的面吹牛弄痹,可吹牛的內(nèi)容都是我干的饭入。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼肛真,長吁一口氣:“原來是場噩夢啊……” “哼谐丢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚓让,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤乾忱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后历极,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窄瘟,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年趟卸,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹄葱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡锄列,死狀恐怖图云,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邻邮,我是刑警寧澤竣况,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站筒严,受9級特大地震影響丹泉,放射性物質(zhì)發(fā)生泄漏情萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一摹恨、第九天 我趴在偏房一處隱蔽的房頂上張望紫岩。 院中可真熱鬧,春花似錦睬塌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贪磺,卻和暖如春硫兰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寒锚。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工劫映, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刹前。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓泳赋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喇喉。 傳聞我的和親對象是個殘疾皇子祖今,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容