Go语言第七章
控制程序的生命周期
管理可复用的资源池
创建可以处理任务的goroutine池
我们会学习 3 个可以在实际工程里使用的包,这3 个包分别实现了不同的并发模式。
- runner包
runner包用于展示如何使用通道来监视程序的执行时间。
如何创建一个Runner类型的值:
//New返回一个新的准备使用的Runner
func New(d time.Duration) *Runner{
return &Runner{
interrupt: make(chan os.Signal,1),
complete: make(chan error),
timeout: time.After(d),
}
}
通道 interrupt 被初始化为缓冲区容量为 1 的通道。这可以保证通道至少能接收一个来自语言运行时的 os.Signal 值,确保语言运行时发送这个事件的时候不会被阻塞。如果 goroutine没有准备好接收这个值,这个值就会被丢弃。例如,如果用户反复敲 Ctrl+C 组合键,程序只会在这个通道的缓冲区可用的时候接收事件,其余的所有事件都会被丢弃.
01 // 这个示例程序演示如何使用通道来监视
02 // 程序运行的时间,以在程序运行时间过长
03 // 时如何终止程序
03 package main
04
05 import (
06 "log"
07 "time"
09 "github.com/goinaction/code/chapter7/patterns/runner"
10 )
11
12 // timeout 规定了必须在多少秒内处理完成
13 const timeout = 3 * time.Second
14
15 // main 是程序的入口
16 func main() {
17 log.Println("Starting work.")
18
19 // 为本次执行分配超时时间
20 r := runner.New(timeout)
21
22 // 加入要执行的任务
23 r.Add(createTask(), createTask(), createTask())
24
25 // 执行任务并处理结果
26 if err := r.Start(); err != nil {
27 switch err {
28 case runner.ErrTimeout:
29 log.Println("Terminating due to timeout.")
30 os.Exit(1)
31 case runner.ErrInterrupt:
32 log.Println("Terminating due to interrupt.")
33 os.Exit(2)
34 }
35 }
36
37 log.Println("Process ended.")
38 }
39
40 // createTask 返回一个根据 id
41 // 休眠指定秒数的示例任务
42 func createTask() func(int) {
43 return func(id int) {
44 log.Printf("Processor - Task #%d.", id)
45 time.Sleep(time.Duration(id) * time.Second)
46 }
47 }
- pool包
这个包用于展示如何使用有缓冲的通道实现资源池,来管理可以在任意数量的goroutine之间共享及独立使用的资源。
如果goroutine需要从池里得到这些资源中的一个,它可以从池里申请,使用完后归还到资源池里
在 Go 1.6 及之后的版本中,标准库里自带了资源池的实现(sync.Pool)。
- Work包
work 包的目的是展示如何使用无缓冲的通道来创建一个 goroutine 池,这些 goroutine 执行并控制一组工作,让其并发执行。
01 // 这个示例程序展示如何使用 work 包
02 // 创建一个 goroutine 池并完成工作
03 package main
04
05 import (
06 "log"
07 "sync"
08 "time"
09
10 "github.com/goinaction/code/chapter7/patterns/work"
11 )
12
13 // names 提供了一组用来显示的名字
14 var names = []string{
15 "steve",
16 "bob",
17 "mary",
18 "therese",
19 "jason",
20 }
21
22 // namePrinter 使用特定方式打印名字
23 type namePrinter struct {
24 name string
25 }
26
27 // Task 实现 Worker 接口
28 func (m *namePrinter) Task() {
29 log.Println(m.name)
30 time.Sleep(time.Second)
31 }
32
33 // main 是所有 Go 程序的入口
34 func main() {
35 // 使用两个 goroutine 来创建工作池
36 p := work.New(2)
37
38 var wg sync.WaitGroup
39 wg.Add(100 * len(names))
40
41 for i := 0; i < 100; i++ {
42 // 迭代 names 切片
43 for _, name := range names {
44 // 创建一个 namePrinter 并提供
45 // 指定的名字
46 np := namePrinter{
47 name: name,
48 }
49
50 go func() {
51 // 将任务提交执行。当 Run 返回时
52 // 我们就知道任务已经处理完成
53 p.Run(&np)
54 wg.Done()
55 }()
56 }
57 }
58
59 wg.Wait()
60
61 // 让工作池停止工作,等待所有现有的
62 // 工作完成
63 p.Shutdown()
64 }
- 小结
可以使用通道来控制程序的声明周期
带defalut分支的select语句可以用来尝试向通道发送和接收数据,而不会阻塞。
有缓冲的通道可以用来管理一组可复用的资源。
语言运行时会处理好通道的协作和同步。
使用无缓冲的通道来创建完成工作的goroutine池。
任何时间都可以用无缓冲的通道来让两个goroutine交换数据,在通道操作完成时一定保证对方接受到了数据。