ch14_併發concurrency

併發concurrency

簡單示例

  • 任何一個函數在調用前加go便可以實現併發
  • 注意如果不加任何通信,由於go所執行的協程並不會阻塞主函數的執行,主函數會同時繼續向後執行,直到結束時退出。
func Hello(name string) {
    fmt.Println("Hello", name)
}

func main() {
    //一般情況下這段代碼不會有任何輸出,因爲在調用輸出函數之前main函數已經結束了
    go Hello("Bob")
}

Channel

Goroutine 奉行通過通信來共享內存,而不是共享內存來通信。

創建channel

    c := make(chan int)

使用channel

    c <- 1 //向c中寫入一個數字1
    tmp := <-c //從c中讀出一個數字並存到一個新變量tmp中
  • channel是引用類型,傳遞參數傳的是引用
  • 讀/寫 無緩存 channel會造成阻塞

關閉channel

    close(c)

示例改進

func Hello(c chan bool, name string) {
    fmt.Println("Hello", name)
    c <- true
}

func main() {
    c := make(chan bool)
    go Hello(c, "Bob")
    <-c
}

遍歷channel

  • 遍歷channel一定要注意能正確關閉channel,否則可能會發生死鎖
    for v := range c {
        fmt.Println(v)
    }

有緩存channel

  • 區別
    • 無緩存channel讀寫之間是同步的,是相互阻塞的,寫入channel數據的操作會等待取出channel數據運行完之後才結束
    • 有緩存channel讀寫是異步的,即寫入操作只要成功寫入後就結束,不管讀取操作成功執行與否
    c := make(chan int) //無緩存channel
    d := make(chan int, 1) //有緩存channel,緩存容量爲1

以下代碼運行時將沒有輸出

func Hello(c chan bool, name string) {
    fmt.Println("Hello", name)
    <-c
}
func main() {
    //此處如果去掉1或者把1改爲0,就會變成同步操作
    c := make(chan bool, 1)
    go Hello(c, "Bob")
    //異步通信,數據寫入完就結束,不管其他協程是否取出數據
    c <- true
}

一個多協程通信的例子

func Counter(c chan bool, index int) {
    a := 1
    for i := 0; i < 100000000; i++ {
        a += i
    }
    fmt.Println(index, a)
    c <- true
}
func main() {
    t1 := time.Now()
    //設置使用的CPU核心數
    //runtime.GOMAXPROCS(runtime.NumCPU())
    //c := make(chan bool, 10) //有緩存
    c := make(chan bool) //無緩存
    for i := 0; i < 10; i++ {
        go Counter(c, i)
    }
    for i := 0; i < 10; i++ {
        <-c
    }
    t2 := time.Now()
    fmt.Println(t2.Sub(t1))
}
  • 雖然一些教材上說goruntime默認運行在一個CPU核心上,但是考慮到教材內容無法與時俱進,經過自己嘗試後發現是否添加runtime.GOMAXPROCS(runtime.NumCPU())程序運行時間差異不大,而將參數設爲1後會明顯變慢,故猜測當前版本默認肯定不是單核心。具體解釋需要在官方文檔找到答案纔好(原諒本菜鳥不知怎麼找官方解釋……)

Select

  • 類似於switch語句,但case都是channel的相關操作,用來監聽和channel有關的IO操作,當 IO 操作發生時,觸發相應的動作。
  • 可處理一個或多個 channel 的發送與接收
  • 同時有多個可用的 channel時按隨機順序處理
  • 可用空的 select 來阻塞 main 函數
  • 可設置超時
func main() {
    c1, c2 := make(chan int), make(chan string)
    o := make(chan bool)
    go func() {
        for {
            select {
            case v, ok := <-c1:
                if ok == false {
                    o <- true
                    break
                }
                fmt.Println("c1", v)
            case v, ok := <-c2:
                if ok == false {
                    o <- true
                    break
                }
                fmt.Println("c2", v)
            }
        }
    }()
    c1 <- 1
    c2 <- "Hello"
    c1 <- 2
    c2 <- "Test"
    close(c1)
    close(c2)
    <-o
}
//一個用select隨機生成01串的例子
func main() {
    c := make(chan int)
    go func() {
        for v := range c {
            fmt.Print(v)
        }
    }()
    for i := 0; i < 10; i++ {
        select {
        case c <- 0:
        case c <- 1:
        }
    }
    close(c)
}
//在select中判斷超時
func main() {
    c := make(chan int)
    select {
    case v := <-c:
        fmt.Println(v)
    case <-time.After(3 * time.Second):
        fmt.Println("Timeout")
    }
    close(c)
}

同步

*需要導入sync包

sync.WaitGroup

  • 可以用其創建任務組
    wg := sync.WaitGroup{} //創建一個任務組
    wg.add(10) //增加10個任務
    wg.Done()  //完成一個任務
    wg.Wait()  //等待任務執行結束
  • 由於是值拷貝,所以需要傳入指針

一個多協程同步的例子

func Counter(wg *sync.WaitGroup, index int) {
    a := 1
    for i := 0; i < 100000000; i++ {
        a += i
    }
    fmt.Println(index, a)
    wg.Done()
}
func main() {
    t1 := time.Now()
    //runtime.GOMAXPROCS(runtime.NumCPU())
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go Counter(&wg, i)
    }
    wg.Wait()
    t2 := time.Now()
    fmt.Println(t2.Sub(t1))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章