g at 2015-10-13 13:59:56.025608644 +0800 CST
Sum from 0 to 10 is starting at 2015-10-13 13:59:56.025508327 +0800 CST
Sum from 2 to 12 is starting at 2015-10-13 13:59:56.025574486 +0800 CST
Sum from 1 to 11 is starting at 2015-10-13 13:59:56.025593711 +0800 CST
Sum from 4 to 14 is 85 at 2015-10-13 14:00:07.030611465 +0800 CST
Sum from 3 to 13 is 75 at 2015-10-13 14:00:08.031926629 +0800 CST
Sum from 0 to 10 is 45 at 2015-10-13 14:00:09.036724803 +0800 CST
Sum from 2 to 12 is 65 at 2015-10-13 14:00:10.038125044 +0800 CST
Sum from 1 to 11 is 55 at 2015-10-13 14:00:11.040366206 +0800 CST
为了演示 chan 的阻塞情况, 上面的代码中特意加了一些 time.Sleep 函数.
- 每个执行 Sum 函数的协程都会运行 10 秒
- main函数中每隔 1 秒读一次 chan 中的数据
从打印结果我们可以看出, 所有协程几乎是同一时间开始的, 说明了协程确实是并发的.
其中, 最快的协程(Sum from 4 to 14…)执行了 11 秒左右, 为什么是 11 秒左右呢?
说明它阻塞在了 Sum 函数中的第一行上, 等了 1 秒之后, main 函数开始读出 chan 中数据后才继续运行.
它自身运行需要 10 秒, 加上等待的 1 秒, 正好 11 秒左右.
最慢的协程执行了 15 秒左右, 这个也很好理解, 总共启动了 5 个协程, main 函数每隔 1 秒 读出一次 chan, 最慢的协程等待了 5 秒,
再加上自身执行了 10 秒, 所以一共 15 秒左右.
到这里, 我们很自然会想到能否增加 chan 的容量, 从而使得每个协程尽快执行, 完成自己的操作, 而不用等待, 消除由于 main 函数的处理所带来的瓶颈呢?
答案是当然可以, 而且在 golang 中实现还很简单, 只要在创建 chan 时, 指定 chan 的容量就行.
package main
import (
"fmt"
"time"
)
func main() {
var ch = make(chan string, 10)
for i := 0; i < 5; i++ {
go sum(i, i+10, ch)
}
for i := 0; i < 10; i++ {
time.Sleep(time.Second * 1)
fmt.Print(<-ch)
}
}
func sum(start, end int, ch chan string) int {
ch <- fmt.Sprintf("Sum from %d to %d is starting at %s\n", start, end, time.Now().String())
var sum int = 0
for i := start; i < end; i++ {
sum += i
}
time.Sleep(time.Second * 10)
ch <- fmt.Sprintf("Sum from %d to %d is %d at %s\n", start, end, sum, time.Now().String())
return sum
}
执行结果如下:
$ go run main.go
Sum from 0 to 10 is starting at 2015-10-13 14:22:14.64534265 +0800 CST
Sum from 2 to 12 is starting at 2015-10-13 14:22:14.645382961 +0800 CST
Sum from 3 to 13 is starting at 2015-10-13 14:22:14.645408947 +0800 CST
Sum from 4 to 14 is starting at 2015-10-13 14:22:14.645417257 +0800 CST
Sum from 1 to 11 is starting at 2015-10-13 14:22:14.645427028 +0800 CST
Sum from 1 to 11 is 55 at 2015-10-13 14:22:24.6461138 +0800 CST
Sum from 3 to 13 is 75 at 2015-10-13 14:22:24.646330223 +0800 CST
Sum from 2 to 12 is 65 at 2015-10-13 14:22:24.646325521 +0800 CST
Sum from 4 to 14 is 85 at 2015-10-13 14:22:24.646343061 +0800 CST
Sum from 0 to 10 is 45 at 2015-10-13 14:22:24.64634674 +0800 CST
从执行结果可以看出, 所有协程几乎都是 10秒完成的. 所以在使用协程时, 记住可以通过使用缓存来进一步提高并发性.
并发时的超时
并发编程, 由于不能确保每个协程都能及时响应, 有时候协程长时间没有响应, 主进程不可能一直等待, 这时候就需要超时机制.
在 golang 中, 实现超时机制也很简单.
package main
import (
"fmt"
"time"
)
func main() {
var ch = make(chan string, 1)
var timeout = make(chan bool, 1)
go sum(1, 10, ch)
go func() {
time.Sleep(time.Second * 5) // 5 秒超时
timeout <- true
}()
select {
case sum := <-ch:
fmt.Print(sum)
case <-timeout:
fmt.Println("Sorry, TIMEOUT!")
}
}
func sum(start, end int, ch chan string) int {
var sum int = 0
for i := start; i < end; i++ {
sum += i
}
time.Sleep(time.Second * 10)
ch <- fmt.Sprintf("Sum from %d to %d is %d\n", start, end, sum)
return sum
}
通过一个匿名函数来控制超时, 然后同时启动 计算 sum 的协程和timeout协程, 在 select 中看谁先结束,
如果 timeout 结束后, 计算 sum 的协程还没有结束的话, 就会进入超时处理.
上例中, timeout 只有5秒, sum协程会执行10秒, 所以执行结果如下:
$ go run main.go
Sorry, TIMEOUT!
修改 time.Sleep(time.Second * 5) 为 time.Sleep(time.Second * 15) 的话, 就会看到 sum 协程的执行结果