Go語(yǔ)言基礎(chǔ)06——并發(fā)編程

goroutine

并行(parallel):指在同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行。
并發(fā)(concurrency):指在同一時(shí)刻只能有一條指令執(zhí)行,但多個(gè)進(jìn)程指令被快速的輪換執(zhí)行,使得在宏觀上具有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果,但在微觀上并不是同時(shí)執(zhí)行的,只是把時(shí)間分成若干段,使多個(gè)進(jìn)程快速交替的執(zhí)行。

有人把Go比作21世紀(jì)的C語(yǔ)言,第一是因?yàn)镚o語(yǔ)言設(shè)計(jì)簡(jiǎn)單,第二,21世紀(jì)最重要的就是并行程序設(shè)計(jì),而Go從語(yǔ)言層面就支持了并行。同時(shí),并發(fā)程序的內(nèi)存管理有時(shí)候是非常復(fù)雜的,而Go語(yǔ)言提供了自動(dòng)垃圾回收機(jī)制。

Go語(yǔ)言為并發(fā)編程而內(nèi)置的上層API基于CSP(communicating sequential processes, 順序通信進(jìn)程)模型。這就意味著顯式鎖都是可以避免的,因?yàn)镚o語(yǔ)言通過(guò)相冊(cè)安全的通道發(fā)送和接受數(shù)據(jù)以實(shí)現(xiàn)同步,這大大地簡(jiǎn)化了并發(fā)程序的編寫(xiě)。

一般情況下,一個(gè)普通的桌面計(jì)算機(jī)跑十幾二十個(gè)線程就有點(diǎn)負(fù)載過(guò)大了,但是同樣這臺(tái)機(jī)器卻可以輕松地讓成百上千甚至過(guò)萬(wàn)個(gè)goroutine進(jìn)行資源競(jìng)爭(zhēng)。
goroutine是Go并行設(shè)計(jì)的核心。goroutine說(shuō)到底其實(shí)就是協(xié)程,但是它比線程更小,十幾個(gè)goroutine可能體現(xiàn)在底層就是五六個(gè)線程,Go語(yǔ)言?xún)?nèi)部幫你實(shí)現(xiàn)了這些goroutine之間的內(nèi)存共享。執(zhí)行g(shù)oroutine只需極少的棧內(nèi)存(大概是4~5KB),當(dāng)然會(huì)根據(jù)相應(yīng)的數(shù)據(jù)伸縮。也正因?yàn)槿绱?,可同時(shí)運(yùn)行成千上萬(wàn)個(gè)并發(fā)任務(wù)。goroutine比thread更易用、更高效、更輕便。

只需在函數(shù)調(diào)?語(yǔ)句前添加 go 關(guān)鍵字,就可創(chuàng)建并發(fā)執(zhí)?單元。開(kāi)發(fā)?員無(wú)需了解任何執(zhí)?細(xì)節(jié),調(diào)度器會(huì)自動(dòng)將其安排到合適的系統(tǒng)線程上執(zhí)行。

在并發(fā)編程里,我們通常想講一個(gè)過(guò)程切分成幾塊,然后讓每個(gè)goroutine各自負(fù)責(zé)一塊工作。當(dāng)一個(gè)程序啟動(dòng)時(shí),其主函數(shù)即在一個(gè)單獨(dú)的goroutine中運(yùn)行,我們叫它main goroutine。新的goroutine會(huì)用go語(yǔ)句來(lái)創(chuàng)建。

package main

import (
    "fmt"
    "time"
)

func newTask() {
    for {
        fmt.Println("this is newTask thread")
        time.Sleep(time.Second)
    }
}

func main() {

    fmt.Println("并發(fā)編程演示案例")

    // 1. 創(chuàng)建goroutine
    go newTask()
    for {
        fmt.Println("this is main goroutine thread")
        time.Sleep(time.Second)
    }

}

主goroutine退出后,其它的工作goroutine也會(huì)自動(dòng)退出,因此可能會(huì)導(dǎo)致:主協(xié)程先退出導(dǎo)致子協(xié)程沒(méi)來(lái)得及調(diào)用

package main

import (
    "fmt"
    "time"
)

func newTask() {
    for {
        fmt.Println("this is newTask thread")
        time.Sleep(time.Second)
    }
}

func main() {

    fmt.Println("并發(fā)編程演示案例")

    // 1. 主goroutine退出后,其它的工作goroutine也會(huì)自動(dòng)退出,因此可能會(huì)導(dǎo)致:主協(xié)程先退出導(dǎo)致子協(xié)程沒(méi)來(lái)得及調(diào)用
    go newTask()
    var i int = 0
    for {
        if i < 5 {
            fmt.Println("this is main goroutine thread ", i)
            time.Sleep(time.Second)
            i++
        } else {
            break
        }

    }

}

控制臺(tái)輸出:

并發(fā)編程演示案例
this is main goroutine thread  0
this is newTask thread
this is main goroutine thread  1
this is newTask thread
this is main goroutine thread  2
this is newTask thread
this is newTask thread
this is main goroutine thread  3
this is newTask thread
this is main goroutine thread  4

runtime.Gosched() 用于讓出CPU時(shí)間片,讓出當(dāng)前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其他等待的任務(wù)運(yùn)行,并在下次某個(gè)時(shí)候從該位置恢復(fù)執(zhí)行。

這就像跑接力賽,A跑了一會(huì)碰到代碼runtime.Gosched() 就把接力棒交給B了,A歇著了,B繼續(xù)跑。

package main

import (
    "fmt"
    "runtime"
)

func main() {

    fmt.Println("runtime.Gosched的使用演示案例")

    // 1. runtime.Gosched的使用
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("this is newTask goroutine ", i)
        }
    }()

    for i := 0; i < 2; i++ {
        runtime.Gosched()
        fmt.Println("this is main goroutine ", i)
    }

    // 輸出內(nèi)容:
    // this is newTask goroutine  0
    // this is newTask goroutine  1
    // this is newTask goroutine  2
    // this is newTask goroutine  3
    // this is newTask goroutine  4
    // this is main goroutine  0
    // this is main goroutine  1
}

調(diào)用 runtime.Goexit() 將立即終止當(dāng)前 goroutine 執(zhí)?,調(diào)度器確保所有已注冊(cè) defer延遲調(diào)用被執(zhí)行。

package main

import (
    "fmt"
    "runtime"
)

func test() {
    fmt.Println("dddddddddddd")
    runtime.Goexit() // 終止所在協(xié)程
    fmt.Println("eeeeeeeeeeee")
}

func main() {

    fmt.Println("runtime.Gosched的使用演示案例")

    // 1. runtime.Goexit的使用
    //調(diào)用 runtime.Goexit() 將立即終止當(dāng)前 goroutine 執(zhí)?,調(diào)度器確保所有已注冊(cè) defer延遲調(diào)用被執(zhí)行。
    go func() {
        fmt.Println("aaaaaaaaaaaa")
        test()
        fmt.Println("bbbbbbbbbbbb")
    }()

    for { //死循環(huán),目的是不讓主協(xié)程結(jié)束

    }

    // 輸出內(nèi)容:
    // aaaaaaaaaaaa
    // dddddddddddd
}

調(diào)用 runtime.GOMAXPROCS() 用來(lái)設(shè)置可以并行計(jì)算的CPU核數(shù)的最大值,并返回之前的值。

package main

import (
    "fmt"
    "runtime"
)

func main() {

    fmt.Println("untime.GOMAXPROCS的使用演示案例")
    max := runtime.GOMAXPROCS(4) //調(diào)用 runtime.GOMAXPROCS() 用來(lái)設(shè)置可以并行計(jì)算的CPU核數(shù)的最大值,并返回之前的值。
    fmt.Println("max: ", max)    //max:  4

    for {
        go fmt.Print(1)
        fmt.Print(0)
    }

    // 輸出內(nèi)容:如果gomaxprocs設(shè)置為1則,
    // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // 0011111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 輸出內(nèi)容:如果gomaxprocs設(shè)置為4則,
    // 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // 0000000000000000000000000000000111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 1111111111111000000000000000000000000000000000000000000000000000000000000000000000000000
    // 0000000000000000000000000000000000000000111111111111111111111111111111111111111111111111
}

多任務(wù)資源競(jìng)爭(zhēng)問(wèn)題:打印機(jī)問(wèn)題

package main

import (
    "fmt"
    "time"
)

func HpPrinter(str string) { // 惠普打印機(jī)
    for _, c := range str {
        time.Sleep(time.Second)
        fmt.Printf("%c", c)
    }
}

func main() {
    go HpPrinter("hello") // 小明去打印hello
    go HpPrinter("ABC")   //小麗打印ABC
    for {

    }

    // 輸出結(jié)果:并不是自己想要的結(jié)果
    //AheBCllo
    //hABelClo

}

為了解決這個(gè)問(wèn)題,就引出了channel

channel

goroutine運(yùn)行在相同的地址空間,因此訪問(wèn)共享內(nèi)存必須做好同步。goroutine 奉行通過(guò)通信來(lái)共享內(nèi)存,而不是共享內(nèi)存來(lái)通信。

引?類(lèi)型 channel 是 CSP 模式的具體實(shí)現(xiàn),用于多個(gè) goroutine 通訊。其內(nèi)部實(shí)現(xiàn)了同步,確保并發(fā)安全。
和map類(lèi)似,channel也一個(gè)對(duì)應(yīng)make創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用。

當(dāng)我們復(fù)制一個(gè)channel或用于函數(shù)參數(shù)傳遞時(shí),我們只是拷貝了一個(gè)channel引用,因此調(diào)用者何被調(diào)用者將引用同一個(gè)channel對(duì)象。和其它的引用類(lèi)型一樣,channel的零值也是nil。

定義一個(gè)channel時(shí),也需要定義發(fā)送到channel的值的類(lèi)型。channel可以使用內(nèi)置的make()函數(shù)來(lái)創(chuàng)建:
make(chan Type) //等價(jià)于make(chan Type, 0)
make(chan Type, capacity)

當(dāng) capacity= 0 時(shí),channel 是無(wú)緩沖阻塞讀寫(xiě)的,當(dāng)capacity> 0 時(shí),channel 有緩沖、是非阻塞的,直到寫(xiě)滿(mǎn) capacity個(gè)元素才阻塞寫(xiě)入。

channel通過(guò)操作符<-來(lái)接收和發(fā)送數(shù)據(jù),發(fā)送和接收數(shù)據(jù)語(yǔ)法:
channel <- value //發(fā)送value到channel
<-channel //接收并將其丟棄
x := <-channel //從channel中接收數(shù)據(jù),并賦值給x
x, ok := <-channel //功能同上,同時(shí)檢查通道是否已關(guān)閉或者是否為空

默認(rèn)情況下,channel接收和發(fā)送數(shù)據(jù)都是阻塞的,除非另一端已經(jīng)準(zhǔn)備好,這樣就使得goroutine同步變的更加的簡(jiǎn)單,而不需要顯式的lock。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string)

    defer fmt.Println("主線程調(diào)用結(jié)束")

    go func() {
        defer fmt.Println("子協(xié)程調(diào)用結(jié)束")
        for i := 0; i < 5; i++ {
            fmt.Println("子協(xié)程調(diào)用中 ", i)
        }
        ch <- "success"
    }()

    data := <-ch //沒(méi)有數(shù)據(jù)前,阻塞
    fmt.Println("主線程取得數(shù)據(jù):", data)

    // 輸出結(jié)果:
    // 子協(xié)程調(diào)用中  0
    // 子協(xié)程調(diào)用中  1
    // 子協(xié)程調(diào)用中  2
    // 子協(xié)程調(diào)用中  3
    // 子協(xié)程調(diào)用中  4
    // 子協(xié)程調(diào)用結(jié)束
    // 主線程取得數(shù)據(jù): success
    // 主線程調(diào)用結(jié)束
}

無(wú)緩沖channel
無(wú)緩沖的通道(unbuffered channel)是指在接收前沒(méi)有能力保存任何值的通道。

這種類(lèi)型的通道要求發(fā)送 goroutine 和接收 goroutine 同時(shí)準(zhǔn)備好,才能完成發(fā)送和接收操作。如果兩個(gè)goroutine沒(méi)有同時(shí)準(zhǔn)備好,通道會(huì)導(dǎo)致先執(zhí)行發(fā)送或接收操作的 goroutine 阻塞等待。

這種對(duì)通道進(jìn)行發(fā)送和接收的交互行為本身就是同步的。其中任意一個(gè)操作都無(wú)法離開(kāi)另一個(gè)操作單獨(dú)存在。

下圖展示兩個(gè) goroutine 如何利用無(wú)緩沖的通道來(lái)共享一個(gè)值:


image.png

l 在第 1 步,兩個(gè) goroutine 都到達(dá)通道,但哪個(gè)都沒(méi)有開(kāi)始執(zhí)行發(fā)送或者接收。
l 在第 2 步,左側(cè)的 goroutine 將它的手伸進(jìn)了通道,這模擬了向通道發(fā)送數(shù)據(jù)的行為。這時(shí),這個(gè) goroutine 會(huì)在通道中被鎖住,直到交換完成。
l 在第 3 步,右側(cè)的 goroutine 將它的手放入通道,這模擬了從通道里接收數(shù)據(jù)。這個(gè) goroutine 一樣也會(huì)在通道中被鎖住,直到交換完成。
l 在第 4 步和第 5 步,進(jìn)行交換,并最終,在第 6 步,兩個(gè) goroutine 都將它們的手從通道里拿出來(lái),這模擬了被鎖住的 goroutine 得到釋放。兩個(gè) goroutine 現(xiàn)在都可以去做別的事情了。

無(wú)緩沖的channel創(chuàng)建格式:
make(chan Type) //等價(jià)于make(chan Type, 0)

如果沒(méi)有指定緩沖區(qū)容量,那么該通道就是同步的,因此會(huì)阻塞到發(fā)送者準(zhǔn)備好發(fā)送和接收者準(zhǔn)備好接收。

package main

import (
    "fmt"
    //"time"
)

func main() {
    ch := make(chan int, 0) //創(chuàng)建一個(gè)無(wú)緩存的channel
    //fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch)) //len(ch)=0,cap(ch)=0

    go func() {
        for i := 0; i < 3; i++ {
            fmt.Println("子協(xié)程:i =", i)
            data := <-ch //讀管道中的內(nèi)容,如果沒(méi)有前,阻塞
            fmt.Println("子協(xié)程取得數(shù)據(jù):data =", data)
        }
    }()
    //time.Sleep(2 * time.Second)
    for i := 0; i < 3; i++ {
        fmt.Println("main協(xié)程:i =", i)
        // 往chan里面寫(xiě)數(shù)據(jù)
        fmt.Println("main協(xié)程發(fā)送數(shù)據(jù):data =", i)
        ch <- i

    }

    // 輸出結(jié)果:
    // main協(xié)程:i = 0
    // main協(xié)程發(fā)送數(shù)據(jù):data = 0
    // 子協(xié)程:i = 0
    // 子協(xié)程取得數(shù)據(jù):data = 0
    // 子協(xié)程:i = 1
    // main協(xié)程:i = 1
    // main協(xié)程發(fā)送數(shù)據(jù):data = 1
    // main協(xié)程:i = 2
    // main協(xié)程發(fā)送數(shù)據(jù):data = 2
    // 子協(xié)程取得數(shù)據(jù):data = 1
    // 子協(xié)程:i = 2
    // 子協(xié)程取得數(shù)據(jù):data = 2
}

有緩沖channel
有緩沖的通道(buffered channel)是一種在被接收前能存儲(chǔ)一個(gè)或者多個(gè)值的通道。

這種類(lèi)型的通道并不強(qiáng)制要求 goroutine 之間必須同時(shí)完成發(fā)送和接收。通道會(huì)阻塞發(fā)送和接收動(dòng)作的條件也會(huì)不同。只有在通道中沒(méi)有要接收的值時(shí),接收動(dòng)作才會(huì)阻塞。只有在通道沒(méi)有可用緩沖區(qū)容納被發(fā)送的值時(shí),發(fā)送動(dòng)作才會(huì)阻塞。

這導(dǎo)致有緩沖的通道和無(wú)緩沖的通道之間的一個(gè)很大的不同:無(wú)緩沖的通道保證進(jìn)行發(fā)送和接收的 goroutine 會(huì)在同一時(shí)間進(jìn)行數(shù)據(jù)交換;有緩沖的通道沒(méi)有這種保證。
示例圖如下:


有緩存的channel.png

l 在第 1 步,右側(cè)的 goroutine 正在從通道接收一個(gè)值。
l 在第 2 步,右側(cè)的這個(gè) goroutine獨(dú)立完成了接收值的動(dòng)作,而左側(cè)的 goroutine 正在發(fā)送一個(gè)新值到通道里。
l 在第 3 步,左側(cè)的goroutine 還在向通道發(fā)送新值,而右側(cè)的 goroutine 正在從通道接收另外一個(gè)值。這個(gè)步驟里的兩個(gè)操作既不是同步的,也不會(huì)互相阻塞。
l 最后,在第 4 步,所有的發(fā)送和接收都完成,而通道里還有幾個(gè)值,也有一些空間可以存更多的值。

有緩沖的channel創(chuàng)建格式:
make(chan Type, capacity)

如果給定了一個(gè)緩沖區(qū)容量,通道就是異步的。只要緩沖區(qū)有未使用空間用于發(fā)送數(shù)據(jù),或還包含可以接收的數(shù)據(jù),那么其通信就會(huì)無(wú)阻塞地進(jìn)行。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int, 3)                                 //創(chuàng)建一個(gè)有緩存的channel
    fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch)) //len(ch)=0,cap(ch)=3

    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
            fmt.Printf("子協(xié)程[%d],len(ch)=%d,cap(ch)=%d\n", i, len(ch), cap(ch))
        }
    }()
    time.Sleep(2 * time.Second)
    for i := 0; i < 10; i++ {
        num := <-ch
        fmt.Println("num = ", num)
    }

    // 輸出結(jié)果:

    // 子協(xié)程[0],len(ch)=1,cap(ch)=3
    // 子協(xié)程[1],len(ch)=2,cap(ch)=3
    // 子協(xié)程[2],len(ch)=3,cap(ch)=3
    // num =  0
    // num =  1
    // num =  2
    // num =  3
    // 子協(xié)程[3],len(ch)=3,cap(ch)=3
    // 子協(xié)程[4],len(ch)=0,cap(ch)=3
    // 子協(xié)程[5],len(ch)=1,cap(ch)=3
    // 子協(xié)程[6],len(ch)=2,cap(ch)=3
    // 子協(xié)程[7],len(ch)=3,cap(ch)=3
    // num =  4
    // num =  5
    // num =  6
    // num =  7
    // num =  8
    // 子協(xié)程[8],len(ch)=3,cap(ch)=3
    // 子協(xié)程[9],len(ch)=0,cap(ch)=3
    // num =  9
}

關(guān)閉channel:
如果發(fā)送者知道,沒(méi)有更多的值需要發(fā)送到channel的話,那么讓接收者也能及時(shí)知道沒(méi)有多余的值可接收將是有用的,因?yàn)榻邮照呖梢酝V共槐匾慕邮盏却?。這可以通過(guò)內(nèi)置的close函數(shù)來(lái)關(guān)閉channel實(shí)現(xiàn)。
注意點(diǎn):
l channel不像文件一樣需要經(jīng)常去關(guān)閉,只有當(dāng)你確實(shí)沒(méi)有任何發(fā)送數(shù)據(jù)了,或者你想顯式的結(jié)束range循環(huán)之類(lèi)的,才去關(guān)閉channel;
l 關(guān)閉channel后,無(wú)法向channel 再發(fā)送數(shù)據(jù)(引發(fā) panic 錯(cuò)誤后導(dǎo)致接收立即返回零值);
l 關(guān)閉channel后,可以繼續(xù)向channel接收數(shù)據(jù);
l 對(duì)于nil channel,無(wú)論收發(fā)都會(huì)被阻塞。

package main

import (
    "fmt"
)

func main() {

    ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i //往chan里面寫(xiě)數(shù)據(jù)
            if i == 5 {
                close(ch)
                //ch <- 10,err 關(guān)閉通道就不能寫(xiě)入東西了
                break
            }
        }

    }()

    for {
        if num, ok := <-ch; ok == true {
            fmt.Println("num = ", num)
        } else {
            fmt.Println("通道被關(guān)閉了,程序結(jié)束")
            break
        }

    }
    // 輸出結(jié)果:
    // num =  0
    // num =  1
    // num =  2
    // num =  3
    // num =  4
    // num =  5
    // 通道被關(guān)閉了,程序結(jié)束
}

通過(guò)range遍歷channel內(nèi)容

package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println("往chan里面寫(xiě)數(shù)據(jù)")
            ch <- i //往chan里面寫(xiě)數(shù)據(jù)

            if i == 5 {
                close(ch)
                //ch <- 10,err 關(guān)閉通道就不能寫(xiě)入東西了
                break
            }
            time.Sleep(time.Second)
        }

    }()

    for num := range ch {
        fmt.Println("遍歷channel:", num)
    }
    // 輸出結(jié)果:

    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 0
    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 1
    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 2
    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 3
    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 4
    // 往chan里面寫(xiě)數(shù)據(jù)
    // 遍歷channel: 5

}

單向channel特點(diǎn)

默認(rèn)情況下,通道是雙向的,也就是,既可以往里面發(fā)送數(shù)據(jù)也可以同里面接收數(shù)據(jù)。

但是,我們經(jīng)常見(jiàn)一個(gè)通道作為參數(shù)進(jìn)行傳遞而值希望對(duì)方是單向使用的,要么只讓它發(fā)送數(shù)據(jù),要么只讓它接收數(shù)據(jù),這時(shí)候我們可以指定通道的方向。

單向channel變量的聲明非常簡(jiǎn)單,如下:
var ch1 chan int // ch1是一個(gè)正常的channel,不是單向的
var ch2 chan<- float64 // ch2是單向channel,只用于寫(xiě)float64數(shù)據(jù)
var ch3 <-chan int // ch3是單向channel,只用于讀取int數(shù)據(jù)

l chan<- 表示數(shù)據(jù)進(jìn)入管道,要把數(shù)據(jù)寫(xiě)進(jìn)管道,對(duì)于調(diào)用者就是輸出。
l <-chan 表示數(shù)據(jù)從管道出來(lái),對(duì)于調(diào)用者就是得到管道的數(shù)據(jù),當(dāng)然就是輸入。

可以將 channel 隱式轉(zhuǎn)換為單向隊(duì)列,只收或只發(fā),不能將單向 channel 轉(zhuǎn)換為普通 channel:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int) //定義一個(gè)channel,默認(rèn)是雙向的

    var writeCh chan<- int = ch // 只能寫(xiě),不能讀
    var readCh <-chan int = ch  //只能讀,不能寫(xiě)
    writeCh <- 666              //ok
    //n := <-writeCh              //err, invalid operation: <-writeCh (receive from send-only type chan<- int)

    num := <-readCh
    // readCh <- 777 //err,invalid operation: readCh <- 777 (send to receive-only type <-chan int)
    fmt.Println(num)
}

單向channel的應(yīng)用:

package main

import (
    "fmt"
    "time"
)

// 生產(chǎn)者,寫(xiě)能寫(xiě)不能讀
func producer(out chan<- int) {
    for i := 0; i < 5; i++ {
        fmt.Println("生產(chǎn)者producer:", (i * i))
        out <- i * i
        time.Sleep(time.Second)
    }
    close(out)
}

// 消費(fèi)者,寫(xiě)能讀不能寫(xiě)
func consumer(in <-chan int) {
    for num := range in {
        fmt.Println("消費(fèi)者consumer:", num)
    }
}

func main() {
    ch := make(chan int) //定義一個(gè)channel,默認(rèn)是雙向的
    go producer(ch)
    consumer(ch)
    fmt.Println("程序結(jié)束")

    // 輸出結(jié)果:
    // 生產(chǎn)者producer: 0
    // 消費(fèi)者consumer: 0
    // 生產(chǎn)者producer: 1
    // 消費(fèi)者consumer: 1
    // 生產(chǎn)者producer: 4
    // 消費(fèi)者consumer: 4
    // 生產(chǎn)者producer: 9
    // 消費(fèi)者consumer: 9
    // 生產(chǎn)者producer: 16
    // 消費(fèi)者consumer: 16
    // 程序結(jié)束
}

Timer

Timer是一個(gè)定時(shí)器,代表未來(lái)的一個(gè)單一事件,你可以告訴timer你要等待多長(zhǎng)時(shí)間,它提供一個(gè)channel,在將來(lái)的那個(gè)時(shí)間那個(gè)channel提供了一個(gè)時(shí)間值。

Timer實(shí)現(xiàn)延時(shí)功能、定時(shí)器停止、定時(shí)器重置

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. Timer的使用
    fmt.Println("------Timer的使用---------")
    fmt.Println("Timer的使用演示案例")
    t1 := time.NewTimer(2 * time.Second)
    fmt.Println("當(dāng)前時(shí)間:", time.Now()) // 2019-03-20 14:00:41.9098831 +0800 CST m=+0.001982001
    t2 := <-t1.C
    fmt.Println("t2:", t2) //2019-03-20 14:00:43.9099262 +0800 CST m=+2.002025101

    fmt.Println("------Timer實(shí)現(xiàn)延時(shí)功能---------")

    // 2. Timer實(shí)現(xiàn)延時(shí)功能
    // 定時(shí)5秒
    t3 := time.NewTimer(5 * time.Second)
    fmt.Println("5s倒計(jì)時(shí)開(kāi)始")
    <-t3.C
    fmt.Println("5s時(shí)間到")

    // 3. 定時(shí)器停止
    fmt.Println("------定時(shí)器停止---------")
    t4 := time.NewTimer(3 * time.Second)
    fmt.Println("3s后執(zhí)行子協(xié)程")
    go func() {
        <-t4.C
        fmt.Println("時(shí)間到,子協(xié)程打印")
    }()

    t4.Stop() // 如果加了這句話,則子協(xié)程的任務(wù)就會(huì)取消,也就是不會(huì)打印

    // 4. 定時(shí)器重置
    fmt.Println("------定時(shí)器重置---------")
    fmt.Println("定時(shí)器重置5s->1s")
    t5 := time.NewTimer(5 * time.Second)
    t5.Reset(1 * time.Second) //重新設(shè)置為1s
    <-t5.C
    fmt.Println("定時(shí)器重置success")
    for {

    }

}

Ticker

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. Ticker的使用
    fmt.Println("Ticker的使用演示案例")

    t1 := time.NewTicker(1 * time.Second)

    i := 0
    for {
        <-t1.C
        fmt.Println("ticker:", i)
        if i > 10 {
            break
        } else {
            i++
        }
    }

    // 輸出結(jié)果:
    // Ticker的使用演示案例
    // ticker: 0
    // ticker: 1
    // ticker: 2
    // ticker: 3
    // ticker: 4
    // ticker: 5
    // ticker: 6
    // ticker: 7
    // ticker: 8
    // ticker: 9
    // ticker: 10
    // ticker: 11

}

Select

Go里面提供了一個(gè)關(guān)鍵字select,通過(guò)select可以監(jiān)聽(tīng)channel上的數(shù)據(jù)流動(dòng)。

select的用法與switch語(yǔ)言非常類(lèi)似,由select開(kāi)始一個(gè)新的選擇塊,每個(gè)選擇條件由case語(yǔ)句來(lái)描述。

與switch語(yǔ)句可以選擇任何可使用相等比較的條件相比, select有比較多的限制,其中最大的一條限制就是每個(gè)case語(yǔ)句里必須是一個(gè)IO操作,大致的結(jié)構(gòu)如下:
select {
case <-chan1:
// 如果chan1成功讀到數(shù)據(jù),則進(jìn)行該case處理語(yǔ)句
case chan2 <- 1:
// 如果成功向chan2寫(xiě)入數(shù)據(jù),則進(jìn)行該case處理語(yǔ)句
default:
// 如果上面都沒(méi)有成功,則進(jìn)入default處理流程
}

在一個(gè)select語(yǔ)句中,Go語(yǔ)言會(huì)按順序從頭至尾評(píng)估每一個(gè)發(fā)送和接收的語(yǔ)句。

如果其中的任意一語(yǔ)句可以繼續(xù)執(zhí)行(即沒(méi)有被阻塞),那么就從那些可以執(zhí)行的語(yǔ)句中任意選擇一條來(lái)使用。

如果沒(méi)有任意一條語(yǔ)句可以執(zhí)行(即所有的通道都被阻塞),那么有兩種可能的情況:
l 如果給出了default語(yǔ)句,那么就會(huì)執(zhí)行default語(yǔ)句,同時(shí)程序的執(zhí)行會(huì)從select語(yǔ)句后的語(yǔ)句中恢復(fù)。
l 如果沒(méi)有default語(yǔ)句,那么select語(yǔ)句將被阻塞,直到至少有一個(gè)通信可以進(jìn)行下去。

通過(guò)select實(shí)現(xiàn)斐波那契數(shù)列

package main

import (
    "fmt"
)

func fibonacci(ch chan<- int, quit <-chan bool) {

    x, y := 1, 1
    for {
        select {
        case ch <- x:
            x, y = y, x+y
        case flag := <-quit:
            fmt.Println("quit:", flag)
            return
        }

    }

}

func main() {
    // 1.通過(guò)select實(shí)現(xiàn)斐波那契數(shù)列 1、1、2、3、5、8、13、21、34、……
    fmt.Println("select的使用演示案例")

    ch := make(chan int)
    quit := make(chan bool)

    go func() {
        for i := 0; i < 8; i++ {
            num := <-ch
            fmt.Println("num = ", num)
        }
        quit <- true

    }()

    fibonacci(ch, quit)

    // 輸出結(jié)果:
    // select的使用演示案例
    // num =  1
    // num =  1
    // num =  2
    // num =  3
    // num =  5
    // num =  8
    // num =  13
    // num =  21
    // quit: true
}

select實(shí)現(xiàn)的超時(shí)機(jī)制
有時(shí)候會(huì)出現(xiàn)goroutine阻塞的情況,那么我們?nèi)绾伪苊庹麄€(gè)程序進(jìn)入阻塞的情況呢?我們可以利用select來(lái)設(shè)置超時(shí),通過(guò)如下的方式實(shí)現(xiàn):

package main

import (
    "fmt"
    "time"
)

func main() {
    // 1. select實(shí)現(xiàn)的超時(shí)機(jī)制
    fmt.Println("select實(shí)現(xiàn)的超時(shí)機(jī)制演示案例")

    ch := make(chan int)
    quit := make(chan bool)

    go func() {
        for {
            select {
            case num := <-ch:
                fmt.Println("程序執(zhí)行中", num)
            case <-time.After(3 * time.Second):
                fmt.Println("程序超時(shí)")
                quit <- true

            }
        }
    }()

    for i := 0; i < 10; i++ {
        ch <- i
        time.Sleep(time.Second)
    }

    <-quit
    fmt.Println("程序結(jié)束")

    // 輸出結(jié)果:
    // select實(shí)現(xiàn)的超時(shí)機(jī)制演示案例
    // 程序執(zhí)行中 0
    // 程序執(zhí)行中 1
    // 程序執(zhí)行中 2
    // 程序執(zhí)行中 3
    // 程序執(zhí)行中 4
    // 程序執(zhí)行中 5
    // 程序執(zhí)行中 6
    // 程序執(zhí)行中 7
    // 程序執(zhí)行中 8
    // 程序執(zhí)行中 9
    // 程序超時(shí)
    // 程序結(jié)束
}

END

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容