对golang并发的简单阐述


goroutine的简单使用

goroutine 是 Go并发能力的核心要素。

将他命名为goroutine 是因为已有的短语——线程、协程、进程等等传递了不准确的含义。goroutine 有简单的模型:它是与其他 goroutine 并行执行的,有着相同地址空间的函数。它是轻量的,仅比分配栈空间多一点点消耗。而初始时栈是很小的,所以它们也是廉价ᡸ的,且随着需要在堆空间上分配和释放。

使用时,goroutine 是一个普通的函数,只是需要使用关键字 go 作为开头。

ready("Tea", 2) // 普通函数调用
go ready("Tea", 2) // ready() 作为 goroutine 运行

下面的程序让一个函数作为两个 goroutine 执行,goroutine 等待一段时间,然后打印一些内容到屏幕。在第6 和 7行,启动了 goroutine。main 函数等待足够的长的时间,这样每个goroutine 会打印各自的文本到屏幕。现在是在第 9 行等待 5 秒钟,但实际上没有任何办法知道,当所有 goroutine 都已经退出应当等待多久。

func ready(w string , sec int) {                                 // 1
    time.Sleep(time.Duration(sec) * time.Second)    // 2
    fmt.Println(w, "is ready!")                                   // 3
}                                                                             // 4
func main() {                                                          // 5
    go ready("Tea", 2)                                             // 6	
    go ready("Coffee", 1) 		              // 7
    fmt.Println("I'm waiting")                                  // 8
    time.Sleep(5 * time.Second)                             // 9
}				             // 10

输出:

I'm waiting // 立刻
Coffee is ready! // 1 秒后
Tea is ready! // 2 秒后

channel进行goroutine的通信

如果不等待 goroutine 的执行(例如,移除第9行),程序立刻终止,而任何正在执行的goroutine 都会停止。为了修复这个,需要一些能够同 goroutine 通讯的机制。这一机制通过channels 的形式使用。channel 可以与 Unix sehll 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel 类型。定义一个 channel时,也需要定义发送到channel 的值的类型。注意,必须使用 make 创建 channel:

ci := make(chan int)
cs := make(chan string)
cf := make(chan interface {})

从make中可以看到我们赋予channel的类型,创建 channel ci 用于发送和接收整数,创建channel cs 用于字符串,以及 channel cf使用了空接口来满足各种类型。向 channel 发送或接收数据,是通过类似的操作符完成的:<−. 具体作用则依赖于操作符的位置:

ci <− 1 // 发送整数 1 到 channel ci
<−ci // 从 channel ci 接收整数
i := <−ci // 从 channel ci 接收整数,并保存到 i 中

我们用channel来写一个程序:

var c chan int //定义 c 作为 int 型的 channel。就是说:这个 channel 传输整数。注意这个变量是全局的,这样 goroutine 可以访问它;
func ready(w string , sec int) {
    time.Sleep(time.Duration(sec) * time.Second)
    fmt.Println(w, "is ready!")
    c <− 1  // 发送整数 1 到 channel c;
}

func main() {
    c = make(chan int) // 初始化 c
    go ready("Tea", 2) // 用关键字 go 开始一个 goroutine
    go ready("Coffee", 1)
    fmt.Println("I'm waiting, but not too long")
    <−c // 等待,直到从 channel 上接收一个值。注意,收到的值被丢弃了
    <−c // 两个 goroutines,接收两个值
}

select监听channel

如果不知道有启动了多少个 goroutine 怎么办呢?这里有另一个Go 内建的关键字:select。通过 select(和其他东西)可以监听 channel 上输入的数据。

L: for {
    selec t {
    case <−c:
        i++
        if i > 1 {
            break L
        }
    }
}

现在将会一直等待下去。只有当从 channel c 上收到多个响应时才会退出循环 L。

GOMAXPROCS

虽然 goroutine 是并发执行的,但是它们并不是并行运行的。如果不告诉 Go 额外的东西,同一时刻只会有一个 goroutine 执行。利用 runtime.GOMAXPROCS(n) 可以设置goroutine 并行执行的数量。来自文档:

GOMAXPROCS 设置了同时运行的 CPU 的最大数量,并返回之前的设置。如果 n < 1,不会改变当前设置。当调度得到改进后,这将被移除。

如果不希望修改任何源代码,同样可以通过设置环境变量 GOMAXPROCS 为目标值。

more about channel

当在 Go 中用 ch := make(chan bool) 创建 channel 时,bool 型的无缓冲 channel 会被创建。这对于程序来说意味着什么呢?首先,如果读取(value := <−ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<−5)将会被阻塞,直到数据被读出。无缓冲 channel 是在多个 goroutine 之间同步很棒的工具。

不过 Go 也允许指定 channel 的缓冲大小,很简单,就是 channel 可以存储多少元素。ch := make(chan bool, 4),创建了可以存储 4 个元素的 bool 型 channel。在这个channel 中,前 4 个元素可以无阻塞的写入。当写入第 5个元素时,代码将会阻塞,直到其他 goroutine 从 channel 中读取一些元素,腾出空间。

一句话来说,在 Go 中下面的为 true:

ch := make(chan type, value) //value == 0,无缓冲;value > 0,缓冲value的元素。

当 channel 被关闭后,读取端需要知道这个事情。如下代码演示了如何检查 channel是否被关闭。

x, ok = <−ch

当 ok 被赋值为 true 意味着 channel 尚未被关闭,同时可以读取数据。ok 被赋值为 false表示 channel 被关闭。


文章作者: RickDamon
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 RickDamon !
  目录