Go语言编程基础:goroutine和通道

95 阅读4分钟
//goroutineGo里,每一个并发执行的活动称为goroutine。
当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine。
新的goroutine通过go语句进行创建。
语法上,一个go语句是在普通的函数或者方法调用前加上go关键字前缀。
    go f()

示例:
    package main
    import (
            "fmt"
            "time"
    )
    func sayHello() {
            for {//死循环
                    fmt.Println("Hello!")
                    time.Sleep(time.Millisecond * 500)
            }
    }
    func fib(x int) int {
            if x < 2 {
                    return x
            }
            return fib(x - 1) + fib(x - 2)
    }
    func main() {
            go sayHello()
            fmt.Println(fib(45))
    }
示例中主goroutine顺序执行main函数,新建的goroutine顺序执行sayHello函数。
当main函数返回时,所有的gotoutine都暴力地直接终结,然后程序退出。

//通道

goroutine是Go程序并发的执行体,通道就是它们之间的连接。
通道是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
每一个通道是一个具体类型的通道。(如接收int类型消息的通道:chan int)
使用内置的make函数来创建一个通道:
ch := make(chan int)
像map一样,通道是一个使用make创建的数据结构的引用。
当复制或者作为参数传递到一个函数时,复制的是引用。

通道有主要两个操作:发送和接收。
发送就是一个goroutine传值到通道
接收就是一个goroutine接收通道里的值
ch := make(chan int)
ch <- x   //发送值给通道
a := <-ch //赋值语句中的接收表达式
<-ch      //接收并丢弃结果

通道支持第三个操作:关闭。
它设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了。
一个通道关闭后的发送操作将导致宕机(panic)。
在一个已经关闭的通道上进行接收操作,将获取所有已经发送的值,直到通道为空。
这时任何接收操作会立即完成,同时获取到一个通道元素类型对应的空值。
内置的close函数关闭通道
    close(ch)

无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上
执行接收操作,这时值传送完成,两个goroutine都可以继续执行。
所以如果接收操作先执行,接收方goroutine将会阻塞,直到发送方
goroutine在同一个通道上发送一个值。
使用无缓冲通道进行的通信导致发送和接收gotoutine同步化。
因此,无缓冲通道也称为同步通道。
示例:
    package main

    import (
            "fmt"
    )

    func main() {
            naturals := make(chan int)
            squares := make(chan int)
    
            go func() {
                    for x := 0; x < 100; x++ {
                            naturals <- x
                    }
                    close(naturals)
            }()

            go func() {
                    for {
                            x, ok := <-naturals
                            if !ok {
                                    break
                            }
                            squares <- x * x
                    }
                    close(squares)
            }()

            for {
                    x, ok := <-squares
                    if !ok {
                            break
                    }
                    fmt.Println(x)
            }
    }
接收方会从通道收到两个信息:第一个是值,第二个是布尔值
true代表接收成功
false代表接收操作在一个已经关闭且读取完数据的通道上。

Go语言中提供range循环语法在通道上的迭代。
当通道关闭且数据读完时退出循环。
所以上面的例子可以改成
    package main

    import (
            "fmt"
    )

    func main() {
            naturals := make(chan int)
            squares := make(chan int)

            go func() {
                    for x := 0; x < 100; x++ {
                            naturals <- x
                    }
                    close(naturals)
            }()

            go func() {
                    for x := range naturals {
                            squares <- x * x
                    }
                    close(squares)
            }()

            for x := range squares {
                    fmt.Println(x)
            }
    }


//单项通道

当通道作为函数参数列表时,有三种不的写法:
输出通道(只能输出),输入通道(只能输入),输入输出通道(都可)

    func(c chan int)
    //通道c可以输入输出(即发送方能给c发数据,接收方也能接收c中的数据)

    func(in chan<- int)
    //输入通道in(即仅支持发送方给in发数据)

    func(out <-chan int)
    //输出通道out(仅支持接收方接收out里的数据)

在任何赋值操作中双向通道可赋值给单项通道,反之不行。


//缓冲通道

缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make容量参数设置。
    ch = make(chan string, 3)

缓冲通道的特点:
如果通道已满,发送方会阻塞
如果通道为空,接收方会阻塞
如果通道既不空也不慢,就都不会阻塞

内置函数cap可以获取通道的容量


//select

select的形式和switch很像
但select必须和通道一起使用
    select {
    case <-ch1:
        //...
    case x := <-ch2: //第二个参数可以不接收
        //...
    case ch3 <- y:
        //...
    default:
        //...
    }
select会一直等待,直到某一个case能够通信,并执行(会执行最快完成通信的那个)。
如果有default而其他case阻塞的话,就会直接执行default。