首頁  >  文章  >  後端開發  >  掌握Go語言的並發模式與平行計算

掌握Go語言的並發模式與平行計算

王林
王林原創
2023-11-30 09:30:33733瀏覽

掌握Go語言的並發模式與平行計算

隨著網路的快速發展,大規模分散式系統的需求越來越高,而並發程式設計和平行運算也成為了網路開發者所必須掌握的技能。而Go語言正是一門為支援並發而生的語言,它在並發程式設計和平行運算方面的表現非常優秀。本文將介紹Go語言的並發模式和平行計算,並給出一些實際案例來幫助讀者深入理解。

一、Go語言的並發模式

Go語言的並發模式主要基於goroutine和channel兩個基本構件。 goroutine是一個輕量級的線程,由Go語言的運行時系統(runtime)管理,可以透過go關鍵字啟動,goroutine之間可以透過channel進行通訊。

下面是一個簡單的goroutine與channel的例子:

package main

import "fmt"

func printMsg(msg string, ch chan string) {
    ch <- msg
}

func main() {
    ch := make(chan string)
    msgs := []string{"Hello", "Golang", "Parallel"}

    for _, msg := range msgs {
        go printMsg(msg, ch)
    }

    for i := 0; i < len(msgs); i++ {
        fmt.Println(<-ch)
    }
}

程式碼透過for循環啟動了三個goroutine,分別輸出三個字串。 printMsg函數將字串訊息寫入channel中,main函數再次從channel中讀取。

1.1 管道模​​式

在Go語言中,可以透過管道(pipeline)模式將多個goroutine串聯起來,形成更複雜的並發系統。管道模式的實現方式通常是透過多個goroutine之間的channel通信,將資料從一個goroutine傳遞給另一個goroutine,並在每個goroutine中對資料進行處理和轉換。下面是一個簡單的管道模式範例:

package main

import (
    "fmt"
)

func addOne(in <-chan int, out chan<- int) {
    for val := range in {
        out <- val + 1
    }
    close(out)
} 

func printNums(out <-chan int) {
    for val := range out {
        fmt.Println(val)
    }
}

func main() {    
    nums := []int{1, 2, 3}
    in := make(chan int)
    out := make(chan int)

    go addOne(in, out)

    go printNums(out)

    for _, num := range nums {
        in <- num
    }
    close(in)
}

程式碼透過define 3個goroutine,分別是輸入goroutine、加1處理goroutine和輸出goroutine,addOne函數將輸入channel中的資料加1後寫入輸出channel中,printNums函數從輸出channel讀取資料並輸出。

1.2 選擇模式

Go語言的select語句提供了一種便捷的方式來處理多個channel,即選擇模式(select pattern)。選擇模式允許我們在多個channel中進行非阻塞式的選擇操作,當多個channel中有可讀或可寫入的訊息時,會自動選擇一個進行操作。

下面是一個簡單的選擇模式範例:

package main

import "fmt"

func ping(ch chan<- string) {
    for {
        ch <- "ping"
    }
}

func pong(ch chan<- string) {
    for {
        ch <- "pong"
    }
}

func printer(ch <-chan string) {
    for {
        fmt.Println(<-ch)
    }
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    ch3 := make(chan string)

    go ping(ch1)
    go pong(ch2)
    go printer(ch3)

    for {
        select {
        case msg := <-ch1:
            ch3 <- msg
        case msg := <-ch2:
            ch3 <- msg
        }
    }
}

程式碼中,ping函數和pong函數會向ch1和ch2中分別發送"ping"和"pong"訊息,printer函數讀取ch3中的訊息並輸出。在main函數中,使用select語句監聽ch1和ch2中的訊息,將收到的訊息透過ch3傳遞給printer函數進行輸出。

二、Go語言的平行計算

Go語言內建的平行運算模組包括sync、atomic和context等。 sync和atomic主要使用互斥鎖(Mutex)和原子操作(atomic operation)來控制並發資料訪問,context用於管理goroutine的上下文資訊。以下簡單介紹一下這些模組的使用方法:

2.1 互斥鎖

互斥鎖是保護共享資源的最常用的同步機制之一,也是Go語言最基本的同步機制之一。在Go語言中,可以透過sync套件中的Mutex類型來建立一個互斥鎖。 Mutex類型提供了兩個重要的方法:Lock和Unlock。在存取共享資源之前,需要先呼叫Lock方法以獲得鎖,存取完成後再呼叫Unlock方法釋放鎖。下面是一個簡單的互斥鎖範例:

package main

import (
    "fmt"
    "sync"
)

func addOne(num *int, mutex *sync.Mutex, wg *sync.WaitGroup) {
    mutex.Lock()
    *num += 1
    mutex.Unlock()

    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    var num int

    mutex := &sync.Mutex{}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go addOne(&num, mutex, &wg)
    }

    wg.Wait()
    fmt.Println(num)
}

程式碼中,定義addOne函數來對num變數進行加1操作,加1操作前需要先取得mutex鎖,加1操作後需要釋放mutex鎖。使用WaitGroup來等待所有的goroutine執行完畢並輸出最終的結果。

2.2 原子操作

在高並發場景下,互斥鎖可能會降低程式效能,因此Go語言提供了原子操作來取代互斥鎖。 atomic套件提供了若干原子運算函數,例如AddInt64、CompareAndSwapInt64、SwapInt64等。使用原子操作可以確保對變數的操作不會被其他goroutine打斷,並發執行也不會被影響。以下是一個簡單的原子操作範例:

package main

import (
    "fmt"
    "sync/atomic"
)

func addOne(num *int64, count *int64, done chan bool) {
    for i := 0; i < 1000; i++ {
        atomic.AddInt64(num, 1)
    }
    atomic.AddInt64(count, 1)
    done <- true
}

func main() {
    var num int64
    var count int64
    done := make(chan bool)

    for i := 0; i < 100; i++ {
        go addOne(&num, &count, done)
    }

    for i := 0; i < 100; i++ {
        <-done
    }

    fmt.Printf("num=%d, count=%d
", num, count)
}

程式碼中,使用atomic包的AddInt64函數對num變數進行原子操作,操作完成後再透過done通知主執行緒。 count變數則透過普通的AddInt64函數進行累加操作,最終輸出num和count的值。

2.3 上下文管理

在Go語言中,常常需要在多個goroutine之間傳遞上下文訊息,例如請求ID、逾時設定等。 context包提供了一種簡單的方式來管理goroutine的上下文資訊。在使用context時,通常需要在主goroutine中建立一個父context,在派生goroutine時透過WithCancel、WithDeadline、WithValue等函數來建立子context並傳遞對應的上下文資訊。下面是一個簡單的上下文管理範例:

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("worker %d canceled
", id)
            return
        default:
            fmt.Printf("worker %d is working
", id)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())

    for i := 0; i < 3; i++ {
        go worker(ctx, i)
    }

    time.Sleep(5 * time.Second)

    cancel()
}

程式碼中,使用context套件建立一個父context,並透過WithCancel函數建立子context。在worker函數中,使用select語句監聽ctx.Done()的訊號,當ctx.Done()被關閉時,表示context被取消,worker函數需要退出。在main函數中透過cancel函數關閉子context,並等待子context被取消。運行結果如下:

worker 0 is working
worker 1 is working
worker 2 is working
worker 2 canceled
worker 1 canceled
worker 0 canceled

當父context被取消時,所有的子context都會收到通知並退出執行。

三、結語

本文簡單介紹了Go語言的並發模式和平行計算,並介紹了goroutine、channel、互斥鎖、原子操作和context等基本構件和模組。透過學習這些基礎知識,我們可以更好地掌握Go語言的並行和平行編程,為建立高效能、高並發的網路應用打下基礎。

以上是掌握Go語言的並發模式與平行計算的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn