小例子全方位感受go通道Channels

Channels用来同步并发执行的函数并提供它们某种传值交流的机制。

Channels的一些特性:通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。

c<-123,把值123输入到管道 c,<-c,把管道 c 的值读取到左边,value :=<-c,这样就是读到 value里面。

管道分类
无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的。
main本身也是一个goroutine

例子1:声明缓冲和无缓冲通道

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    done2 := make(chan bool, 1)

    fmt.Print(done1, done2)
}

例子二: 无缓冲在同一个main里面的 死锁例子

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    done1 <- true  /** 这句是输入值,它会一直阻塞,等待读取 */
    <-done1 /** 这句是读取,因为没有其它的协程来读取done1,上面已经阻死了,永远走不到这里 */
    fmt.Print("finish")
}

例子三:仅有输入语句,但没读取语句 的死锁例子

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    done1 <- true //一直在这里等待读取
    fmt.Print("finish")
}

例子四:仅有读取语句,但没输入语句的死锁例子

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    <-done1
    fmt.Print("finish")
}

例子五:协程的阻死,不会影响main

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    go func() {
        <-done1 //匿名函数的这个管道一直阻塞
    }()
    fmt.Print("finish")
}

例子六:无缓冲channel在协程 go routine 里面阻塞死

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    go func() {
        println("我可能会输出哦") /** 阻塞前的语句 */
        done1 <- true
        println("我永远不会输出")
        <-done1 /** 这句也不会走到,除非在别的协程里面读取,或者在 main */
    }()
    fmt.Print("finish")
}

例子七:在 例子六 的基础上演示,延时 main 的跑完

package main

import (
    "fmt"
    "time"
)

func main() {
    done1 := make(chan bool)
    go func() {
        println("我可能会输出哦") /** 阻塞前的语句 */
        done1 <- true
        println("我永远不会输出")
        <-done1 /** 这句也不会走到,除非在别的协程里面读取,或者在 main */
    }()
    time.Sleep(time.Second * 1)
    fmt.Print("finish")
}

例子八:演示无缓冲channel 在不同的goroutine位置里面接收填充和接收

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    go func() {
        done1 <- true
        println("我永远不会输出,除非 <-done 执行")
    }()
    <-done1 /** 这里接收,在输出finish之前,那么上面的语句将会走通 */
    fmt.Print("finish")
}

控制台输出: 我永远不会输出,除非 <-done 执行
finish

例子九:将<-done1往下挪一行,finish会先输出

```
package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    go func() {
        done1 <- true
        println("我永远不会输出,除非 <-done 执行")
    }()
    fmt.Print("finish")
    <-done1 /** 这里接收,在输出finish之后,那么上面的语句将会走通 */
}

控制台输出:finish
我永远不会输出,除非 <-done 执行

例子十:没缓存的 channel 使用 close 后,不会阻塞

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool)
    close(done1)
    //done1 <- true   //通道关闭了不能输入

    <-done1
    fmt.Print("finish")
}

这里很好理解,就像假如一个很长的水龙头,你关了它,水管的水不会再进来,但水龙头没留完的水会继续流完

例子十一:有缓冲的 channel 不会阻塞的例子

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool, 1)
    done1 <- true
    <-done1
    fmt.Print("finish")
}

例子十二:有缓冲的 channel 会阻塞的例子

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool, 1)
    //done1 <- true
    <-done1  /** 虽然是有缓冲的,但是在没输入的情况下,读取,会阻塞 */
    fmt.Print("finish")
}

例子十三:有缓冲的 channel 会阻塞的例子(输入通道的值数量超过缓冲值)

package main

import (
    "fmt"
)

func main() {
    done1 := make(chan bool, 1)
    <-true
    <-false  /** 放第二个值的时候,第一个还没被人拿走,这时候才会阻塞,根据缓冲值而定 */
    fmt.Print("finish")
}

例子十四:多channel模式

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := getMessagesChannel("第一", 600)
    c2 := getMessagesChannel("第二", 500)
    c3 := getMessagesChannel("第三", 5000)

    /** 层层限制阻塞 */
    /** 这个 for 里面会造成等待输入,c1 会阻塞 c2 ,c2 阻塞 c3 */
    /** 所以它总是,先输出 c1 然后是 c2 最后是 c3 */
    for i := 1; i <= 3; i++ {
        /** 每次循环提取一轮,共三轮 */
        println(<-c1) /** 除非 c1 有输入值,否则就阻塞下面的 c2,c3 */
        println(<-c2) /** 除非 c2 有输入值,否则就阻塞下面的 c3 */
        println(<-c3) /** 除非 c3 有输入值,否则就阻塞进入下一轮循环,反复如此 */
    }
}

func getMessagesChannel(msg string, delay time.Duration) <-chan string {
    c := make(chan string)
    go func() {
        for i := 1; i <= 3; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Millisecond * delay) /** 仅仅起到,下一次的 c 在何时输入 */
        }
    }()
    return c
}

这个程序的运行结果,首轮的,第一,第二,第三 很快输出,因为getMessagesChannel 函数的延时 在 输入值之后,在第二轮及其之后因为下一个 c3 要等到 5秒后才能输入,所以会阻塞第二轮循环的开始5秒,如此反复。
修改:如果把 getMessagesChannel 里面的延时,放在输入值之前,那么 c3 总是等待 5秒 后输出

于是我们的神器select出来了,哪个gotoutine准备好了哪个先执行

例子十五:select多复路通道

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := getMessagesChannel("第一", 600)
    c2 := getMessagesChannel("第二", 500)
    c3 := getMessagesChannel("第三", 5000)
     //多复路select每次只输出一个通道,所以需输出9次
    for i := 1; i <= 9; i++ {
        select {
        case msg := <-c1:
            println(msg)
        case msg := <-c2:
            println(msg)
        case msg := <-c3:
            println(msg)
        }
    }
}

func getMessagesChannel(msg string, delay time.Duration) <-chan string {
    c := make(chan string)
    go func() {
        for i := 1; i <= 3; i++ {
            c <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Millisecond * delay) /** 仅仅起到,下一次的 c 在何时输入 */
        }
    }()
    return c
}

分析:前3次输出,“第一”,“第二”,“第三”,都有,而且是随机顺序输出;因为协程的调度;
第4,5,6次,由于“第二”只延时 500ms, 比 600ms 和 5000ms 都要小,那么它先输出,然后是“第一”,此时“第三”还不能输出,因为它还在等5秒。此时已经输出5次,再过 500ms,”第三”的5秒还没走完,所以继续输出”第一”, 再过 100ms,500+100=600,”第二”也再完成了一次,那么输出。
至此,”第一”和”第二”已经把管道的 3 个值全部输出,9-7 = 2,剩下两个是 “第三”。此时,距离首次的 5000ms 完成,还有,500-600-600 = 3800ms,达到后,”第三” 将输出,再过5秒,最后一次”第三输出”

上面的例子大家都go run main.go执行下体会下,特别是例子十四和例子十五应该特别有感觉!

猜你喜欢

转载自blog.csdn.net/suresand/article/details/79633926
今日推荐