Go:21---goroutine併發執行體
一、Go併發方式
- Go有兩種併發程式設計的風格,分別是:goroutine和通道(channel)
- 它們支援通訊順序程式(CSP),CSP是一個併發的模式,在不同的執行體(goroutine)之間傳遞值,但是變數本身侷限於單一的執行體
二、goroutine介紹
- 在Go裡,每一個併發執行的活動稱為goroutine
- 考慮一個程式,它有兩個函式,一個做一些計算工作,另一個將結果輸出,假設它們不互相呼叫。順序程式可能呼叫一個函式,然後呼叫另一個,但是在有兩個或多個goroutine的併發程式中,兩個函式可以同時執行
goroutine與執行緒
- 如果你使用過作業系統或者其他語言中的執行緒,可以假設goroutine類似於執行緒,Go在幕後使用執行緒來管理併發
- goroutine和執行緒之間在數量上有非常大的差別,建立一個goroutine只需佔用幾KB的記憶體,因此即便建立數千個goroutine也不會耗盡記憶體。另外,建立和銷燬goroutine的效率也非常高
主goroutine
- 當一個程式啟動時,只有一個goroutine來呼叫main函式,稱它為主goroutine
go語句
- 一個go語句是在普通的函式或者方法呼叫前加上go關鍵字字首
- go語句使函式在一個新建立的goroutine中呼叫,go語句本身的執行立即完成,不需要阻塞等待
- 例如:
f() // 呼叫f(), 需要阻塞等待返回 go f() // 新建一個呼叫f()的goroutine, 程式不需要阻塞等待
三、goroutine的執行週期
- goroutine會一直執行,直到本身函式結束,或者主程式結束
演示案例
- 下面是一個例子,在goroutine所執行的函式還沒有執行完的時候,main就執行結束了,因此goroutine也跟隨著結束
package main import ( "fmt" "time" ) func slowFunc() { // 延時2秒 time.Sleep(time.Second * 2) fmt.Println("sleeper() finished") } func main() { go slowFunc() fmt.Println("I am now shown straightaway!") }
- 上面程式的執行效果如下,可以看到slowFunc()還未執行完,但是由於main()函式執行結束了,因此goroutine也跟隨著結束
- 我們可以修改main()函式來阻塞等待slowFunc()執行完,這種方法比較簡單
package main import ( "fmt" "time" ) func slowFunc() { // 延時2秒 time.Sleep(time.Second * 2) fmt.Println("sleeper() finished") } func main() { go slowFunc() fmt.Println("I am now shown straightaway!") time.Sleep(time.Second * 3) // 阻塞等待goroutine任務完成 }
四、演示案例(斐波那契)
package main
import (
"fmt"
"time"
)
func main() {
// 不用阻塞等待, 開啟一個新的goroutine執行該函式
go spinner(100 * time.Millisecond)
// 計算斐波那契, 一段時間後返回
const n = 45
fibN := fib(n)
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/`{
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x - 1) + fib(x - 2)
}
- 當fib()函式沒有執行完的時候,一直執行spinner()函式列印內容
- 當fib()函式執行完之後,main()函式結束,程式終止,列印內容,如下所示
五、演示案例(併發時鐘伺服器)
- 現在我們有這樣一個服務端程式:等待客戶端的連線,當客戶端連線之後會每秒鐘向客戶端傳送一次當前時間
- 下面我們有2種設計服務端:
- 不使用goroutine::此時服務端只能接受一個客戶端的連線,只有噹噹前的客戶端斷開連線之後才可以處理下一個客戶端
- 使用goroutine:服務端可以同時接收多個客戶端的連線,並併發的給所有客戶端傳送資料
- 下面是一個客戶端程式netcat.go,可以連線到指定的服務端,從服務端接收到資料然後列印到標準輸出上
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
// 連線服務端
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}
func mustCopy(dst io.Writer, src io.Reader) {
// 把接收到的資料列印到標準輸出上
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
不使用goroutine
- 服務端的程式如下,命名為clock1.go
package main import ( "io" "log" "net" "time" ) func main() { // 建立tcp服務端 listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { // 阻塞接收客戶端的連線 conn, err := listener.Accept() if err != nil { log.Print(err) continue } // 接收到一個客戶端之後, 呼叫該函式處理該客戶端 handleConn(conn) } } func handleConn(c net.Conn) { defer c.Close() for { // 向客戶端傳送當前內容, 內容為當前時間 _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) if err != nil { return } // 休眠1秒 time.Sleep(1 * time.Second) } }
- 執行上面的服務端程式,程式會阻塞等待客戶端的連線:
- 使用上面的客戶端程式netcat去連線服務端,下面開啟了2個客戶端,但是由於服務端只能處理一個客戶端的請求,因此下面左側現開啟的客戶端正在接收資料,右側的客戶端只能阻塞等待
- 當我們結束左側的客戶端之後,右側的客戶端得以連線到服務端,然後接收資料
- 從上面我們可以看出,服務端沒有使用goroutine,因此其只能阻塞在處理客戶端的handleConn()函式中
使用goroutine
- 如果想要讓服務端同時處理多個服務端,只需要做一點修改就可以了,那就是在服務端呼叫handleConn()函式之前加上go關鍵字,讓handleConn()函式在一個goroutine中進行執行,從而不阻塞主程式的執行
package main import ( "io" "log" "net" "time" ) func main() { listener, err := net.Listen("tcp", "localhost:8000") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { log.Print(err) continue } // 只修改了這個地方, 其他地方沒有任何修改 go handleConn(conn) } } func handleConn(c net.Conn) { defer c.Close() for { _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) if err != nil { return } time.Sleep(1 * time.Second) } }
- 將上面的服務端程式編譯好重新執行,如下:
- 現在我們可以使用多個客戶端去連線服務端了,可以同時接收到資料,如下所示:
六、演示案例(併發回聲伺服器)
- 待續
相關文章
- Go高效併發 10 | Context:多執行緒併發控制神器GoContext執行緒
- Java併發(一)----程式、執行緒、並行、併發Java執行緒並行
- 66.QT-執行緒併發、QTcpServer併發、QThreadPool執行緒池QT執行緒TCPServerthread
- Java併發(四)----執行緒執行原理Java執行緒
- 程式併發執行的特徵特徵
- Thread 併發執行例項thread
- JAVA多執行緒併發Java執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- 多執行緒併發篇——如何停止執行緒執行緒
- python基礎執行緒-管理併發執行緒Python執行緒
- 多執行緒併發執行及解決方法執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- 常用高併發網路執行緒模型效能優化實現-體驗百萬級高併發執行緒模型設計執行緒模型優化
- nodejs 單執行緒 高併發NodeJS執行緒
- Java併發——執行緒池ThreadPoolExecutorJava執行緒thread
- Java併發系列 — 執行緒池Java執行緒
- python併發執行request請求Python
- Rust的併發執行緒 - ibraheemRust執行緒
- 聊聊併發(五)——執行緒池執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- [Java併發]執行緒的並行等待Java執行緒並行
- go併發 - channelGo
- Go 併發操作Go
- go 併發 mapGo
- Go 併發 -- 通道Go
- Go併發原理Go
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- 【高併發】深入理解執行緒的執行順序執行緒
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- 執行緒 並行 與 併發 的區別執行緒並行
- 多執行緒與併發----Semaphere同步執行緒
- MySQL多執行緒併發調優MySql執行緒
- Spring如何處理執行緒併發Spring執行緒
- Java併發(十六)----執行緒八鎖Java執行緒
- Fork Join 併發任務執行框架框架
- 併發與多執行緒基礎執行緒