天天看點

go| go channel講解 1

很榮幸收到 慕課網

内容分享邀請, 試錄 go channel 講解主題的視訊, 定位在 初級+基礎, 希望幫助大家快速入門 go, 熟悉 go channel 的使用.

分享大綱:

  • go 開發環境配置
  • go channel 講解: 為什麼會有 go 和 channel, channel 程式設計上的細節
  • 提升篇: 如何開始開發一個大型 go 項目; 深入了解協程之「快」

  • 安裝
  • 配置環境變量

環境變量 是非常常見的一個術語, 也是很多基礎不太好的同學, 不太了解的一個概念. 要了解它其實很簡單:

程序皆有環境 -- 了解UNIX程序

程式運作之後, 在作業系統裡面就是程序了, 而程序都有自己的環境, 環境變量屬于環境的一種, 是程序可以通路到的資源.

其中非常常用的, 是

PATH

變量, 程序通過這個變量來尋找可執行檔案. 如果沒有這個變量的話, 就需要使用可執行檔案的完整路徑了.

比如

go.exe

可執行檔案, 完整路徑是

c:/tools/go/go.exe

, 把

c:/tools/go

加入到

PATH

環境變量裡, 才可以直接執行

go run

等指令, 否則就需要使用

c:/tools/go/go run

.

起步階段, 先隻需要配置

GOROOT

GOPATH

2 個環境變量.

在 window 下配置環境變量有更快捷的操作: 使用

win+r

快捷鍵打開

運作視窗

輸入

sysdm.cpl

, 就可以快速打開環境變量的設定界面.

  • hello go

環境配置好後, 就可以還是

hello world

之旅啦. 推薦 2 款工具:

vscode goland

vscode: hello go

vscode 會自動監測代碼檔案使用的語言, 提示安裝相應的插件, 按照提示安裝即可. 推薦安裝

code runner

插件, 安裝後使用

ctrl+alt+n

就可以快速運作目前檔案

goland: hello go

goland 非常推薦, 強大的代碼提示和很多帶來效率提升的功能, 推薦使用

view | distraction free mode

, 代碼 清爽 的編碼體驗

go channel 講解

  • hello.go: hello world
package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello czl")
}           

使用

go run hello.go

運作

為什麼使用協程

天下武功, 唯快不破

為了體驗協程之快, 需要在

hello.go

做一點改造, 模拟一件耗時的任務, 這樣友善比較

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("hello czl")

    // 普通版
    doSomeThing()
    // 協程版
    go doSomeThing()
}

// 模拟一件耗時的任務
func doSomeThing() {
    time.Sleep(time.Second*1)
}           

隻用添加

go

關鍵詞, 就可以将任務抛給協程來執行, 是不是 hin 簡單

time go run hello.go

分别來比較, 會發現, 協程版快很多 呀

不過如果細心的話, 就會發現這個 快得有問題 -- 耗時任務明明需要 1s, 怎麼協程版居然 1s 内完成了?

改造一下模拟任務的函數, 就可以發現了:

// 模拟一件耗時的任務
func doSomeThing() {
    time.Sleep(time.Second*1)
    // 增加輸出, 友善檢視程式的執行效果
    fmt.Println("do some thing")
}           

這就是 go 程式運作機制導緻的 陷阱 -- go程式運作時, 會依次尋找

package main

->

func main()

, 依次執行 main() 中代碼,

當使用 go 關鍵字把任務交給協程執行後, 程式繼續向下執行, 然而下面并沒有其他需要執行的代碼了, 是以 main() 就退出了, 并沒有等待協程執行完.

問題來了: 怎麼讓我們程式裡的協程執行完呢?

這裡有三種方式:

  • 方式一, 使用 sleep, 等協程執行完, 但是就不好比較協程版和非協程版的耗時了
// 方式一: sleep
go doSomeThing()
time.Sleep(time.Second*4)           
  • 方式二: 使用 channel, 通過讀寫 channel 觸發協程等待
// 方式二: channel
    ch := make(chan bool)
    go func() {
        doSomeThing()
        // 任務執行完了才給 channel 寫資料
        ch <- true
    }()
    // 要等待 channel 裡有資料
    <- ch           

也正是 channel 的這一特性, 在後面 channel功能二: 協程排程 中會更詳細的講解

  • 方式三: 調用 sync 系統庫裡的 WaitGroup API 實作

官方的定義如下:

A WaitGroup waits for a collection of goroutines to finish.
The main goroutine calls Add to set the number of goroutines to wait for.
Then each of the goroutines runs and calls Done when finished.
At the same time, Wait can be used to block until all goroutines have finished           

用代碼翻譯過來, 是四步:

// 方式三: WaitGroup
    var wg sync.WaitGroup // 第一步: 聲明 WaitGroup
    wg.Add(1) // 第二步: 添加需要等待的協程, 需要等多少個協程, 就添加多少
    go func() {
        // 執行完之後需要做的操作
        defer wg.Done() // 第三步: 協程調用 done() 表示協程執行完了

        // 正常需要執行的邏輯
        doSomeThing()
    }()
    wg.Wait() // 第四步: 什麼地方需要等待協程執行完, 就在上面地方加上 wait()           

這裡使用 go 中的

defer

關鍵字, defer 關鍵字可以在函數執行完後執行.

為什麼要在函數一開始調用卻在函數最後執行呢? 比如函數一開始打開了一個檔案, 最後寫了很多邏輯, 忘了關閉檔案了, 使用 defer 就可以很好的避免這種情況.

準備工作, 或者說 熱身 時間有點長, 來看看協程有多快, 多執行一點任務:

// 快在哪? 多來點任務不就知道了
    for i := 0; i < 4; i++ {
        doSomeThing()
    }

    // 協程版
    var wg sync.WaitGroup
    wg.Add(4)
    for i := 0; i < 4; i++ {
        // wg.Add(1)
        go func() {
            defer wg.Done()

            doSomeThing()
        }()
    }
    wg.Wait()           

time go run hello.go

, 會發現協程版要快很多. 可以把 for 循環再調多點, 感受就更明顯了

> 23:47 src $ time go run hello.go
hello world
do some thing
do some thing
do some thing
do some thing

real    0m4.978s
user    0m0.000s
sys     0m0.015s

> 00:23 src $ time go run hello.go
hello world
do some thing
do some thing
do some thing
do some thing

real    0m2.200s
user    0m0.000s
sys     0m0.031s           

寫在後面

感受到 協程之快 以及 需要多個協程來執行任務 後, 就引入了下一個話題:

為什麼要使用 channel? -- 因為有多個協程.