1個頂1000個:併發起來的程序,如澎湃咆哮的引擎,轟隆隆

引言

現在還不能掌握併發編程的程序員,面臨被計算機技術淘汰的窘境。本文注重介紹go語言的goroutine實現併發的編程。


1個頂1000個:併發起來的程序,如澎湃咆哮的引擎,轟隆隆


什麼是GoRoutine

Goroutine是與其他函數或方法併發運行的函數或方法,可以將Goroutine視為輕量級線程與線程相比,創建Goroutine的成本開銷微乎其微。因此,GO應用程序通常同時運行數千個Goroutine。

與線程相比,Goroutine的優勢

  • 與線程相比,Goroutine非常節約CPU和內存。它們的堆棧大小隻有幾KB,堆棧可以根據應用程序的需要進行擴展和縮小,而對於線程,必須指定並固定堆棧大小
  • Goroutine被多路複用到較少數量的OS線程。具有數千個Goroutine的程序中可能只有一個線程。如果線程中任何Goroutine阻塞並等待用戶輸入,則創建另一個OS線程,並將剩餘的Goroutine移到新的OS線程。所有這些都由運行時(runtime)負責,作為程序員從這些錯綜複雜的細節中抽象出來,並被賦予一個乾淨的API來處理併發性。
  • Goroutine使用通道(channel)進行通信。通道的設計可以防止在使用Goroutine訪問共享內存時出現爭用情況。可以將通道視為Goroutines通信所使用的管道。我們將在下一教程中詳細討論頻道。

開始使用GoRoutine

在函數或方法調用前面加上關鍵字go,您將同時運行一個新的Goroutine。

讓我們創建一個Goroutine:)

<code>package main

import (
"fmt"
)

func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
fmt.Println("main function")
}/<code>

在第11行,go hello()開始一個新的Goroutine,hello()函數將與main()函數併發運行。main函數在它自己的Goroutine中運行,它被稱為main Goroutine。

運行這個程序,你會有驚喜哦!

本程序僅輸出了文本函數。那我們開始的Goroutine怎麼樣了?

我們需要了解goroutine的兩個重要特性,才能理解為什麼會發生這種情況

  • 當啟動新的Goroutine時,goroutine調用立即返回。與函數不同,該控件不等待Goroutine完成執行。在Goroutine調用之後,程序立即返回到下一行代碼,並忽略來自Goroutine的任何返回值。
  • 主Goroutine應該為任何其他Goroutine運行。如果主Goroutine終止,則該程序將終止,並且不會運行其他Goroutine

我想現在你可以理解為什麼我們的Goroutine沒有跑了。

在第11行調用go hello()之後,程序立即返回到下一行代碼,而無需等待hello goroutine完成。

然後,主Goroutine終止,因為沒有其他代碼要執行,因此hello Goroutine沒有機會運行

我們對代碼稍作修改。

<code>package main

import (
"fmt"

"time"
)

func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println("main function")
}/<code>

在上述程序的第13行中,我們調用了time包的Sleep方法,該方法使執行該程序的go例程延時。在這種情況下,主Goroutine會休眠1秒。

現在,go hello()調用在主Goroutine終止之前有足夠的時間執行。此程序首先打印Hello world goroutine,等待1秒,然後打印main函數。

這種在主Goroutine中使用睡眠來等待其他Goroutine完成執行的方式,是我們用來理解Goroutine如何工作的一種技巧。通道(channel)可以用來阻塞主Goroutine,直到所有其他Goroutine完成執行。

啟動多個Goroutine

讓我們再編寫一個啟動多個Goroutine的程序,以便更好地理解Goroutine。

<code>package main

import (
"fmt"
"time"
)

func numbers() {
for i := 1; i <= 5; i++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d ", i)
}
}
func alphabets() {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c ", i)
}
}
func main() {
go numbers()
go alphabets()
time.Sleep(3000 * time.Millisecond)
fmt.Println("main terminated")
}/<code>

上面的程序在行號中啟動兩個Goroutine,代碼21和22行。這兩個Goroutine現在同時運行。

Goroutine numbers 最初休眠250毫秒,然後打印1,然後再次休眠並打印2,相同的週期發生,直到打印5。類似地,Goroutine alphabets 打印從a到e的字母,並且有400毫秒間隔的休眠時間。

主Goroutine啟動numbers() ,alphabets(),然後休眠3000毫秒,然後終止。

上述代碼運行後輸出內容如下:

<code>1 a 2 3 b 4 c 5 d e main terminated  /<code>

代碼不如圖片來的直觀,我們使用下圖描述該程序的工作方式。

1個頂1000個:併發起來的程序,如澎湃咆哮的引擎,轟隆隆

藍色的圖像的第一部分代表 numbers Goroutine,栗色的第二部分代表 alphabets Goroutine,綠色的第三部分代表 main Goroutine,最後的部分將上述三部分合並在一起,並向我們展示了程序是如何工作的。

每個框頂部的0毫秒、250毫秒等表示時間(以毫秒為單位),輸出在每個框的底部表示為1、2、3,依此類推。

藍色框告訴我們,1在250ms之後打印,2在500ms之後打印,依此類推。最後一個綜合框的底部有值1a23b4c5de,它也是程序的輸出。

直觀的時序圖將幫助你更好地理解GoRoutine運行的機制。

Happy Coding :-)


我是 ,持續分享編程故事,歡迎關注。


分享到:


相關文章: