美文网首页打造公链我爱编程
golang的goroutine和channel的简单理解

golang的goroutine和channel的简单理解

作者: 建怀 | 来源:发表于2018-06-14 16:49 被阅读0次

    golang的goroutine和channel的理解

    Go语言的goroutines,信道和死锁

    goroutine

    goroutine有点类似线程,但是更轻。

    使用关键字go来定义并启动一个goroutine:

    func main(){
        go loop()
        loop()
        time.Sleep(time.Second) // main函数太快,为了等待一下goroutine,停顿一秒
    }
    

    如果goroutine在结束的时候,能跟主线程能通信,那就需要信道。

    信道

    简单来说,channel信道是goroutine之间相互通信的东西。用来goroutine之间发消息和接收消息。其实,就是在做goroutine之间的内存共享。

    是用make来建立一个信道:

    var channel chan int = make(chan int)
    // 或者直接声明
    channel := make(chan int)
    

    向信道存消息和取消息,一个例子说明:

    func main(){
        var message chan string = make(chan string)
        go func(message string){
            message <- message // 存消息
        }("Ping!")
        fmt.Println(<-message) // 取消息
    }
    

    信道的存消息和取消息都是阻塞的,也就是说,无缓冲的信道在取消息和存消息的时候都会挂起当前的goroutine,除非另一端已经准备好。

    有了这个阻塞的机制,来实现让goroutine告诉主线程:我执行完了。下面的demo很简单就做到了。

    var complete chan int = make(chan int)
    func loop(){
        for i:=0;i<10;i++{
            fmt.Printf("%d",i)
        }
        completee <- 0 // 执行完毕,发个消息
    }
    func main(){
        go loop()
        <- complete // loop()线程跑完,收到消息,main接着跑
    }
    

    其实,无缓冲的信道永远不会存储数据,只会负责数据的流通。

    • 从无缓冲信道取数据,必须要有数据流进来才可以,否则当前线程阻塞
    • 数据流入无缓冲信道,如果没有其他goroutine取走数据,那么当前线程阻塞

    如果信道正有数据,那还要加入数据,或者信道没有数据,硬要取数据,就会引起死锁。

    死锁

    一个死锁的简单例子:

    func main(){
        ch := make(chan int)
        <- ch // 没有往ch信道存放数据,就开始取数据,就直接阻塞main goroutine,信道ch被锁
    }
    

    其实简单理解就是,一个goroutine,向里面加数据或者存数据,都会锁死信道,并且阻塞当前goroutine,也就是死锁。

    死锁的解决办法也很简单,没取走的数据赶紧取走,没放入的数据一定要放入,无缓冲信道不能承载数据。

    另外一个解决办法就是缓冲信道,即设置channel有一个数据的缓冲大小:

    c := make(chan int,1)
    

    这个也很好理解,c可以缓存一个数据,放入一个,不会阻塞,再放入一个,阻塞,被其他goroutine取走一个,又不阻塞。
    也就是说,只阻塞在容量一定的时候,不达容量就不阻塞。

    非常类似Pyhton里面的Queue的概念。

    无缓冲信道的数据进出顺序

    一句话说清楚:无缓冲信道的数据是先到先出。

    缓冲信道

    缓冲信道在流入的数据数目超过容量的时候,就会死锁。

    可以简单把缓冲信道看成是一个线程安全的队列。

    信道数据读取和信道关闭

    取信道消息可以用range进行遍历:

    for v := range ch{ // ch := make(chan int,3)
        fmt.Println(v)
        if len(ch) <= 0{
            break  // 为了避免信道没消息了,还去取造成死锁。
        }
    }
    
    等待多goroutine的方案

    使用信道阻塞主线,其他goroutine跑完回到主线。

    其实无缓冲信道和缓冲信道都能实现这个需求,伪代码如下:

    package main
    import "fmt"
    
    var quit chan int
    func foo(id int){
        fmt.Println(id)
        quit <- 0
    }
    func main(){
        count := 1000
        quit = make(chan int)
        for i:=0;i<count;i++{
            go foo(i)
        }
        for i:=0;i<count;i++{
            <- quit
        }
    }
    // 无缓冲的信道是一批数据一个一个的流进流出,处理是乱序的
    
    package main
    import "fmt"
    func foo(id int){
        fmt.Println(id)
    }
    func main(){
        count := 1000
        quit := make(chan int,1000)
        for i:=0;i<count;i++{
            foo(i)
            quit <- i
        }
        for i:=0;i<count;i++{
            <- quit
        }
    }
    // 有缓冲的信道是数据一个一个进行处理,处理是顺序的

    相关文章

      网友评论

        本文标题:golang的goroutine和channel的简单理解

        本文链接:https://www.haomeiwen.com/subject/loiyeftx.html