C語言中文網 目錄
首頁 > Go語言教程 > Go語言并發 閱讀:4,782

Go語言goroutine(輕量級線程)

在編寫 Socket 網絡程序時,需要提前準備一個線程池為每一個 Socket 的收發包分配一個線程。開發人員需要在線程數量和 CPU 數量間建立一個對應關系,以保證每個任務能及時地被分配到 CPU 上進行處理,同時避免多個任務頻繁地在線程間切換執行而損失效率。

雖然,線程池為邏輯編寫者提供了線程分配的抽象機制。但是,如果面對隨時隨地可能發生的并發和線程處理需求,線程池就不是非常直觀和方便了。能否有一種機制:使用者分配足夠多的任務,系統能自動幫助使用者把任務分配到 CPU 上,讓這些任務盡量并發運作。這種機制在 Go 語言中被稱為 goroutine

goroutine 的概念類似于線程,但 goroutine 由 Go 程序運行時的調度和管理。Go 程序會智能地將 goroutine 中的任務合理地分配給每個 CPU。

Go 程序從 main 包的 main() 函數開始,在程序啟動時,Go 程序就會為 main() 函數創建一個默認的 goroutine。

使用普通函數創建 goroutine

Go 程序中使用 go 關鍵字為一個函數創建一個 goroutine。一個函數可以被創建多個 goroutine,一個 goroutine 必定對應一個函數。

1) 格式

為一個普通函數創建 goroutine 的寫法如下:

go 函數名( 參數列表 )

  • 函數名:要調用的函數名。
  • 參數列表:調用函數需要傳入的參數。

使用 go 關鍵字創建 goroutine 時,被調用函數的返回值會被忽略。

如果需要在 goroutine 中返回數據,請使用后面介紹的通道(channel)特性,通過通道把數據從 goroutine 中作為返回值傳出。

2) 例子

使用 go 關鍵字,將 running() 函數并發執行,每隔一秒打印一次計數器,而 main 的 goroutine 則等待用戶輸入,兩個行為可以同時進行。請參考下面代碼:
package main

import (
    "fmt"
    "time"
)

func running() {

    var times int
    // 構建一個無限循環
    for {
        times++
        fmt.Println("tick", times)

        // 延時1秒
        time.Sleep(time.Second)
    }

}

func main() {

    // 并發執行程序
    go running()

    // 接受命令行輸入, 不做任何事情
    var input string
    fmt.Scanln(&input)
}
命令行輸出如下:
tick 1
tick 2
tick 3
tick 4
tick 5

代碼執行后,命令行會不斷地輸出 tick,同時可以使用 fmt.Scanln() 接受用戶輸入。兩個環節可以同時進行。

代碼說明如下:
第 12 行,使用 for 形成一個無限循環。
第 13 行,times 變量在循環中不斷自增。
第 14 行,輸出 times 變量的值。
第 17 行,使用 time.Sleep 暫停 1 秒后繼續循環。
第 25 行,使用 go 關鍵字讓 running() 函數并發運行。
第 29 行,接受用戶輸入,直到按 Enter 鍵時將輸入的內容寫入 input 變量中并返回,整個程序終止。

這段代碼的執行順序如下圖所示。


圖:并發運行圖

這個例子中,Go 程序在啟動時,運行時(runtime)會默認為 main() 函數創建一個 goroutine。在 main() 函數的 goroutine 中執行到 go running 語句時,歸屬于 running() 函數的 goroutine 被創建,running() 函數開始在自己的 goroutine 中執行。此時,main() 繼續執行,兩個 goroutine 通過 Go 程序的調度機制同時運作。

使用匿名函數創建goroutine

go 關鍵字后也可以為匿名函數或閉包啟動 goroutine。

1) 使用匿名函數創建goroutine的格式

使用匿名函數或閉包創建 goroutine 時,除了將函數定義部分寫在 go 的后面之外,還需要加上匿名函數的調用參數,格式如下:

go func( 參數列表 ){
    函數體
}( 調用參數列表 )

其中:
  • 參數列表:函數體內的參數變量列表。
  • 函數體:匿名函數的代碼。
  • 調用參數列表:啟動 goroutine 時,需要向匿名函數傳遞的調用參數。

2) 使用匿名函數創建goroutine的例子

在 main() 函數中創建一個匿名函數并為匿名函數啟動 goroutine。匿名函數沒有參數。代碼將并行執行定時打印計數的效果。參見下面的代碼:
package main

import (
    "fmt"
    "time"
)

func main() {

    go func() {

        var times int

        for {
            times++
            fmt.Println("tick", times)

            time.Sleep(time.Second)
        }

    }()

    var input string
    fmt.Scanln(&input)
}
代碼說明如下:
  • 第 10 行,go 后面接匿名函數啟動 goroutine。
  • 第 12~19 行的邏輯與前面程序的 running() 函數一致。
  • 第 21 行的括號的功能是調用匿名函數的參數列表。由于第 10 行的匿名函數沒有參數,因此第 21 行的參數列表也是空的。

提示

所有 goroutine 在 main() 函數結束時會一同結束。

goroutine 雖然類似于線程概念,但是從調度性能上沒有線程細致,而細致程度取決于 Go 程序的 goroutine 調度器的實現和運行環境。

終止 goroutine 的最好方法就是自然返回 goroutine 對應的函數。雖然可以用 golang.org/x/net/context 包進行 goroutine 生命期深度控制,但這種方法仍然處于內部試驗階段,并不是官方推薦的特性。

截止 Go 1.9 版本,暫時沒有標準接口獲取 goroutine 的 ID。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo