Go语言实战——第七章

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交换数据,在通道操作完成时一定保证对方接受到了数据。

上一篇:Go 优势


下一篇:深入了解Golang网络库中socket阻塞调度源码