Golang 学习笔记——Goruntine并发协程

Golang

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

Golang 学习笔记——Goruntine 并发协程

并发程序指同时进行多个任务的程序,Web 服务器会一次处理成千上万的请求。

Go 语言中的并发程序可以用两种手段来实现。本节讲解 goroutine 和 channel,其支持“顺序通信进程”(communicating sequential processes)或被简称为 CSP。

在这种编程模型中值会在不同的运行实例(goroutine)中传递,通过 channel 进行不同 Goroutine 间的数据共享。

可以简单地把 goroutine 类比作一个线程,当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine(主线程)。

新的 Goroutine 会用 go 语句来创建。在语法上,go 语句是一个普通的函数或方法调用前加上关键字 go。go 语句会使其语句中的函数在一个新创建的 goroutine 中运行。而 go 语句本身会迅速地完成。(非阻塞)

f()    // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

主函数返回 时,所有的 goroutine 都会被直接打断,程序退出。除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个 goroutine 来打断另一个的执行。

Go 显示一个加载动画函数,可以放在 go spinner(100 * time.Millisecond) 执行

func spinner(delay time.Duration) {
    for {
        for _, r := range `-\|/` {
            fmt.Printf("\r%c", r)
            time.Sleep(delay)
        }
    }
}

go 后跟的 函数的参数 会在 go 语句自身执行时被求值,就是在 main goroutine 中被求值。

Channels

Channel 是 goruntine 间的通信管道,每个 channel 都有一个可发送数据的类型。

可以使用 make 函数,创建一个 channel:

ch := make(chan int) // ch has type 'chan int'
// 返回一个 chan int 类型的

和 map 类似,channel 也对应一个 make 创建的 底层数据结构的引用。当我们复制一个 channel 或用于函数参数传递时,我们只是拷贝了一个 channel 引用,channel 的零值也是 nil。

发送&接收

ch <- x  // 将 x 发送到 ch
x = <-ch // 从 ch 取出一个值给 x
<-ch     // 从 ch 取出一个值但是 Drop 掉

Channel 支持关闭 close 操作,关闭 channel 后对通道的任何发送操作都会 panic 但进行接收操作仍然可以接收到之前一键成功发送的数据,如果 channel 中已经没有数据了就返回一个零值数据。

chlose(ch)

不带缓存的 Channels

一个无缓冲的 channel 的发送操作会导致发送者的 goroutine 堵塞,直到另一个 goruntine 在相同的 channel 上执行接收操作,堵塞才会解除,反之亦然。

当发送的值通过 channel 成功传输之后,两个 goroutine 才可以继续执行后面的语句。

func main() {
    // 建立 tcp 服务器
    conn, err := net.Dial("tcp", "localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    // 信息事件
    done := make(chan int)
    go func() {
        io.Copy(os.Stdout, conn)
        log.Println("done")
        done <- 1 // 发送一个 goruntine 已经完成的信息
    }()
    mustCopy(conn, os.Stdin)
    conn.Close()
    <-done // 等待后台 goruntine 完成
}

检查 Channels 是否关闭

接收 channels 的语句可以写成:第二个结果的一个 bool ,true 表示成功从 channels 接收到值,false 表示 channels 已经被关闭并且里面没有值可接收。

// 方法一
x, ok := <-channel
if !ok {
    // channel error do something....
}
// 方法二
if x, ok := <-channel;ok{
    // channel succues do something.....
}

因为这种处理模式很常见,Go 语言的 range 循环可以直接在 channels 上面迭代,它依次从 channel 接收数据,当 channel 被关闭并且没有值可接收时跳出循环。

for x := range channel {
    // rev channel to x and do something....
}
// 当 channel 被关闭时会主动跳出循环
close(channel)

不管一个 channel 是否被关闭,当它没有被引用时将会被 Go 语言的垃圾自动回收器回收。所以不需要显性的 close(channel)

不要将关闭一个打开文件的操作和关闭一个 channel 操作混淆。对于每个打开的文件,都需要在不使用的时候调用对应的 Close 方法来关闭文件。

单方向的 Channel

当一个 channel 作为一个函数参数 时,它一般总是被专门用于只发送或者只接收。这是为了防止滥用,可以定义只接收或只发送的 channel ,这种限制将在编译期检测。

chan <- int // 只发送 int 的 channel
<-chan int // 只接收 int 的 channel

close channel 只用于停止向 channel 发送新数据。但不能阻止接收 channel 的数据。所以 对一个只接受的 channel 调用 close 将引起编译错误

channel := make(chan int)
// 单方向的 channel 通常用于函数参数
func inChannel(in <-chan int) // 仅接收
func outChannel(out chan<-int) // 仅发送

调用 inChannel 时 in 的类型将隐式地从 chan int 转换成 chan<- int。任何双向 channel 向单向 channel 变量的赋值操作都将导致该隐式转换。但是反之不能将单向 channel 赋值给 双向 channel

带缓存 (buffer) 的 Channels

ch = make(chan string, 3)

Author: WhaleFall

Permalink: https://www.whaleluo.top/golang/golang-goruntine-note/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments