前言
上回在 用 Go 写一个轻量级的 ssh 批量操作工具 里提及过,我们做 Golang 并发的时候要对并发进行限制,对 goroutine 的执行要有超时控制。那会没有细说,这里展开讨论一下。
以下示例代码全部可以直接在 The Go Playground 上运行测试:
并发
我们先来跑一个简单的并发看看
package main
import (
"fmt"
"time"
)
func run(task_id, sleeptime int, ch chan string) {
time.Sleep(time.Duration(sleeptime) * time.Second)
ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
return
}
func main() {
input := []int{3, 2, 1}
ch := make(chan string)
startTime := time.Now()
fmt.Println("Multirun start")
for i, sleeptime := range input {
go run(i, sleeptime, ch)
}
for range input {
fmt.Println(<-ch)
}
endTime := time.Now()
fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))
}
函数 run() 接受输入的参数,sleep 若干秒。然后通过 go 关键字并发执行,通过 channel 返回结果。
channel 顾名思义,他就是 goroutine 之间通信的“管道"。管道中的数据流通,实际上是 goroutine 之间的一种内存共享。我们通过他可以在 goroutine 之间交互数据。
ch <- xxx // 向 channel 写入数据
<- ch // 从 channel 中读取数据
channel 分为无缓冲(unbuffered)和缓冲(buffered)两种。例如刚才我们通过如下方式创建了一个无缓冲的 channel。
channel 的缓冲,我们一会再说,先看看刚才看看执行的结果。
Multirun start
task id 2 , sleep 1 second
task id 1 , sleep 2 second
task id 0 , sleep 3 second
Multissh finished. Process time 3s. Number of tasks is 3
Program exited.
三个 goroutine `分别 sleep 了 3,2,1秒。但总耗时只有 3 秒。所以并发生效了,go 的并发就是这么简单。
按序返回
刚才的示例中,我执行任务的顺序是 0,1,2。但是从 channel 中返回的顺序却是 2,1,0。这很好理解,因为 task 2 执行的最快嘛,所以先返回了进入了 channel,task 1 次之,task 0 最慢。
如果我们希望按照任务执行的顺序依次返回数据呢?可以通过一个 channel 数组(好吧,应该叫切片)来做,比如这样
package main
import (
"fmt"
"time"
)
func run(task_id, sleeptime int, ch chan string) {
time.Sleep(time.Duration(sleeptime) * time.Second)
ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
return
}
func main() {
input := []int{3, 2, 1}
chs := make([]chan string, len(input))
startTime := time.Now()
fmt.Println("Multirun start")
for i, sleeptime := range input {
chs[i] = make(chan string)
go run(i, sleeptime, chs[i])
}
for _, ch := range chs {
fmt.Println(<-ch)
}
endTime := time.Now()
fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))
}
运行结果,现在输出的次序和输入的次序一致了。
Multirun start
task id 0 , sleep 3 second
task id 1 , sleep 2 second
task id 2 , sleep 1 second
Multissh finished. Process time 3s. Number of tasks is 3
Program exited.
超时控制
刚才的例子里我们没有考虑超时。然而如果某个 goroutine 运行时间太长了,那很肯定会拖累主 goroutine 被阻塞住,整个程序就挂起在那儿了。因此我们需要有超时的控制。
通常我们可以通过select + time.After 来进行超时检查,例如这样,我们增加一个函数 Run() ,在 Run() 中执行 go run() 。并通过 select + time.After 进行超时判断。
package main
import (
"fmt"
"time"
)
func Run(task_id, sleeptime, timeout int, ch chan string) {
ch_run := make(chan string)
go run(task_id, sleeptime, ch_run)
select {
case re := <-ch_run:
ch <- re
case <-time.After(time.Duration(timeout) * time.Second):
re := fmt.Sprintf("task id %d , timeout", task_id)
ch <- re
}
}
func run(task_id, sleeptime int, ch chan string) {
time.Sleep(time.Duration(sleeptime) * time.Second)
ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
return
}
func main() {
input := []int{3, 2, 1}
timeout := 2
chs := make([]chan string, len(input))
startTime := time.Now()
fmt.Println("Multirun start")
for i, sleeptime := range input {
chs[i] = make(chan string)
go Run(i, sleeptime, timeout, chs[i])
}
for _, ch := range chs {
fmt.Println(<-ch)
}
t4((|йA4(Q94(йA5A9хQMQФ4)(4(zp4(4)х4)х4)хā4)5A9х4)A4(4(jbǎ хJ0х_^K^ х_^K_^K>GfC"RV#4(zs"G>GfC"4(4)14(zp4(4)х4)х4)хā4)5A9х4)A4(4(х>G&3_^K х_^K_^KB#r4(r'r'Σ?"3r'rZ3&7B3j4(4)t4(vjzpn":'&7gb{24(4(3zsj>GfC"b1RV#b{B;vV6j3"b.'4(4(|йA4(4(&4(4(brZj3ro疒j惚r'&*rokkR2 |