Go routine 介紹

  • 在go裡面,只要main()函數結束,程式就結束了。
  • 若要使你的函數在背景執行,只要在函數前面加上關鍵字:go 就可以,如: go myFunction()
  • 要使用Go routine 只需要在函數前面加上 go 關鍵字即可。
  • 如果需要go routine 相互通信溝通,則需要使用 channel

一步步解說

請看下面的範例:

package main
import (
  "fmt"
)
func main() {
  fmt.Println("start")
  doSomething()
  fmt.Println("end")
}
func doSomething() {
  fmt.Println("do something")
}

// 可以得到輸出:
start
do something
end

這是很正常的程式執行結果,但當我們加入 go 這個關鍵字讓程式在背景運行時,結果就會完全不同:

func main() {
  fmt.Println(“start”)
  go doSomething()
  fmt.Println(“end”)
}

// 輸出結果:
start
end

竟然與預期不同?要知道go語言中,main()函數結束,程式就結束(很重要,自己念三次),所以我們得到上面結果,doSomething()還來不及輸出,主程式就結束了。解決方法可以加入延遲(time.Sleep())來處理:

package main
import (
    "fmt"
    "time"
)
func printHello() {
    fmt.Println("Hello World!")
}
func main() {
    fmt.Println("main execution started")
    // create goroutine
    go printHello()
    // schedule another goroutine
    time.Sleep(10 * time.Millisecond)
    fmt.Println("main execution stopped")
}

// 輸出
main execution started
Hello World!
main execution stopped

雖然我們可以使用類似time.Sleep()來延遲主程式結束時間,但這並不是一個好的做法,因為我們不知道doSomething()需要多少時間來執行,幸好go提供了標準函數庫sync來讓我們解決這個問題,sync.WaitGroup基本上是一個可以增加或減少的counter,一旦值為0就表示程式執行完畢,所以我們可以更改上面的範例為:

package main
import (
  “fmt”
  “sync”
)

var wg sync.WaitGroup  // 不用New來產生

func doSomething() {
  fmt.Println(“do something”)
  wg.Done() // this is done
}

func main() {
  fmt.Println(“start”)
  wg.Add(1) // 增加1表示有一個事件需要等待
  go doSomething()
  fmt.Println(“end”)
  wg.Wait() // wait for all things to be done
}

// 輸出結果
start
end
do something

由於我們採用背景執行,因此輸出的結果可能與預期的不同,這表示程式內可能有不可預期的執行狀況,這點之後可以使用channel來作解決。採用go myfunc()的好處是:當我有需要同步執行的動作且彼此間不互相影響時(如大量訂單處理),就可以使用go來加快程式的運行,例如:
* 傳統程式使用方式

package main
import (
  "fmt"
  "time"
)

func timestable(x int) {
  for i := 1; i <= 12; i++ {
    fmt.Printf(“%d x %d = %d\n”, i, x, x*i)
    time.Sleep(100 * time.Millisecond)
  }
}

func main() {
  timestable(2)
}

// 輸出
1 x 2 = 2
2 x 2 = 4
3 x 2 = 6
4 x 2 = 8
5 x 2 = 10
6 x 2 = 12
7 x 2 = 14
8 x 2 = 16
9 x 2 = 18
10 x 2 = 20
11 x 2 = 22
12 x 2 = 24

如果把main()改為多次執行:

func main() {
  for n := 2; n <= 12; n++ {
    timestable(n)
  }
}

執行時間約需13秒,但因為這個工作彼此不互相依賴,所以可以同步執行,程式碼可以改為:

package main
import (
  "fmt"
  "sync"
  "time"
)
var wg sync.WaitGroup
func main() {
  for n := 2; n <= 12; n++ {
    wg.Add(1)
    go timestable(n)
  }
  wg.Wait()
}
func timestable(x int) {
  for i := 1; i <= 12; i++ {
    fmt.Printf("%d x %d = %d\n", i, x, x*i)
    time.Sleep(100 * time.Millisecond)
  }
  wg.Done()
}

執行時間大幅縮短為:1.5秒,雖然輸出結果不如預期的順序,但可以確認採用同步背景執行,的確是可以縮短程式運行的時間,這個就是go的好處,若運用的當可以讓你的程式快速執行。

匿名routine(Anonymous goroutines)

package main
import (
    "fmt"
    "time"
)
func main() {
    fmt.Println("main execution started")
    // create goroutine
    go func() {
        fmt.Println("Hello World!")
    }()
    // schedule another goroutine
    time.Sleep(10 * time.Millisecond)
    fmt.Println("main execution stopped")
}

// 輸出
main execution started
Hello World!
main execution stopped

參考函數