首頁  >  文章  >  後端開發  >  Go 中的並發:從基礎知識到高階概念

Go 中的並發:從基礎知識到高階概念

Linda Hamilton
Linda Hamilton原創
2024-10-03 06:11:30426瀏覽

Concurrency in Go: From Basics to Advanced Concepts

目錄

  1. 並發簡介
  2. 併發與並行
  3. Go 程式:並發的建構塊
  4. 通道:Go 程式之間的通訊
  5. Select 語句:管理多個通道
  6. 同步原語
  7. 並發模式
  8. 上下文包:管理取消和 超時。
  9. 最佳實務與常見陷阱**

1.並發簡介

並發是同時處理多個任務的能力。在 Go 中,並發性是一等公民,內建於該語言的核心設計中。 Go 的並發方法是基於通訊順序進程(CSP),該模型強調進程之間的通訊而不是共享記憶體。

2.併發與並行:

Go 例程支援並發,並發是獨立執行進程的組合。
如果系統有多個 CPU 核心並且 Go 運行時安排 go 例程並行運行,則可能會發生並行(同時執行)。

3。 Go 例程:
並發的構建塊是 Go 例程是由 Go 運行時管理的輕量級執行緒。它是與其他函數或方法同時運行的函數或方法。 Go 例程是 Go 並發模型的基礎。

主要特徵:

  • 輕量級:Go 例程比作業系統執行緒輕得多。您可以輕鬆建立數千個 go 例程,而不會顯著影響效能。
  • 由 Go 執行時期管理:Go 排程器處理可用作業系統執行緒之間的 go 例程指派。
  • 廉價建立:啟動 go 例程就像在函數呼叫之前使用 go 關鍵字一樣簡單。
  • 堆疊大小:Go 程式從一個小堆疊(大約 2KB)開始,可以根據需要增長和縮小。

建立 Go 程式:
要啟動 go 例程,只需使用 go 關鍵字,後面接著函數呼叫:

go functionName()

或使用匿名函數:

go func() {
    // function body
}()

Go-routine 調度:

  • Go 運行時使用 M:N 調度程序,其中 M 個 go 例程被調度到 N 個作業系統執行緒上。
  • 這個調度程序是非搶佔式的,這意味著 Go 例程在空閒或邏輯阻塞時會產生控制權。

通訊與同步:

  • Goroutine 通常使用通道進行通信,遵循「不要透過共享記憶體進行通信;透過通信來共享記憶體」的原則。
  • 對於簡單的同步,您可以使用像sync.WaitGroup或sync.Mutex這樣的原語。

範例及說明:

package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}

func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(150 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}

func main() {
    go printNumbers()
    go printLetters()
    time.Sleep(2 * time.Second)
    fmt.Println("\nMain function finished")
}

說明:

  • 我們定義了兩個函數:printNumbers 和 printLetters。
  • 在 main 中,我們使用 go 關鍵字將這些函數作為 goroutine 啟動。
  • 然後 main 函數休眠 2 秒,讓 goroutine 完成。
  • 如果沒有 goroutine,這些函數將按順序運行。對於 goroutine,它們是同時運行的。
  • 輸出將顯示數字和字母交錯,並示範並發執行。

Goroutine 生命週期:

  • goroutine 在使用 go 關鍵字建立時啟動。
  • 當其功能完成或程式退出時,它終止。
  • 如果管理不當,Goroutines 可能會洩漏,因此確保它們可以退出非常重要。

最佳實務:

  • 不要在庫中建立 goroutine;讓呼叫者控制並發。
  • 創建無限數量的 goroutine 時要小心。
  • 使用通道或同步原語在 goroutine 之間進行協調。
  • 考慮使用工作池來有效管理多個 goroutine。

帶有 go 程式解釋的簡單範例

package main

import (
    "fmt"
    "time"
)

// printNumbers is a function that prints numbers from 1 to 5
// It will be run as a goroutine
func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(500 * time.Millisecond) // Sleep for 500ms to simulate work
        fmt.Printf("%d ", i)
    }
}

// printLetters is a function that prints letters from 'a' to 'e'
// It will also be run as a goroutine
func printLetters() {
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(300 * time.Millisecond) // Sleep for 300ms to simulate work
        fmt.Printf("%c ", i)
    }
}

func main() {
    // Start printNumbers as a goroutine
    // The 'go' keyword before the function call creates a new goroutine
    go printNumbers()

    // Start printLetters as another goroutine
    go printLetters()

    // Sleep for 3 seconds to allow goroutines to finish
    // This is a simple way to wait, but not ideal for production code
    time.Sleep(3 * time.Second)

    // Print a newline for better formatting
    fmt.Println("\nMain function finished")
}

4.頻道:

通道是 Go 中的一項核心功能,它允許 go 例程相互通訊並同步執行。它們為一個 go 例程提供了一種將資料傳送到另一個 go 例程的方法。

頻道的目的

Go 中的通道有兩個主要用途:
a) 通訊:它們允許 goroutine 相互發送和接收值。
b) 同步:它們可用於跨 Goroutine 同步執行。

建立:使用 make 函數建立通道:

ch := make(chan int)  // Unbuffered channel of integers

傳送:使用

ch <- 42  // Send the value 42 to the channel

Receiving: Values are received from a channel using the <- operator:

value := <-ch  // Receive a value from the channel

Types of Channels

a) Unbuffered Channels:

  • Created without a capacity: ch := make(chan int)
  • Sending blocks until another goroutine receives.
  • Receiving blocks until another goroutine sends.
ch := make(chan int)
go func() {
    ch <- 42  // This will block until the value is received
}()
value := <-ch  // This will receive the value

b) Buffered Channels:

  • Created with a capacity: ch := make(chan int, 3)
  • Sending only blocks when the buffer is full.
  • Receiving only blocks when the buffer is empty.
ch := make(chan int, 2)
ch <- 1  // Doesn't block
ch <- 2  // Doesn't block
ch <- 3  // This will block until a value is received

Channel Directions

Channels can be directional or bidirectional:

  • Bidirectional: chan T
  • Send-only: chan<- T
  • Receive-only: <-chan T

Example :

func send(ch chan<- int) {
    ch <- 42
}

func receive(ch <-chan int) {
    value := <-ch
    fmt.Println(value)
}

Closing Channels

Channels can be closed to signal that no more values will be sent:

close(ch)

Receiving from a closed channel:

If the channel is empty, it returns the zero value of the channel's type.
You can check if a channel is closed using a two-value receive:

value, ok := <-ch
if !ok {
    fmt.Println("Channel is closed")
}

Ranging over Channels

You can use a for range loop to receive values from a channel until it's closed:

for value := range ch {
    fmt.Println(value)
}

Hey, Thank you for staying until the end! I appreciate you being valuable reader and learner. Please follow me here and also on my Linkedin and GitHub .

以上是Go 中的並發:從基礎知識到高階概念的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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