GoLang 常用模块

2022-10-26 language golang

简单介绍常见的模块,例如 fmt、unsafe、signal、container 等。

fmt

其中最常用的是 %v,这是一个通用的格式化方式,用来打印 struct 的成员变量名称。

%v   默认格式,只打印各个字段的值,没有字段名称;
%+v  当打印结构体时,同时会添加字段名称;
%#v  类似+v,同时会打印对象名称,并用语法标示数据类型,例如字符串会加"";
%T   变量对应的类型名称 pakage.struct;

示例如下。

package main

import (
    "fmt"
)

type User struct {
    name string
    age  int
}

func main() {
    u := &User{name: "andy", age: 30}

    fmt.Printf("%v\n", *u)  // {andy 30}
    fmt.Printf("%+v\n", *u) // {name:andy age:30}
    fmt.Printf("%#v\n", *u) // main.User{name:"andy", age:30}
    fmt.Printf("%T\n", *u)  // main.User
}

flag

os 包中有一个 string 类型的切片变量 os.Args ,其中 os.Args[0] 放的是程序本身的名字,所以实际参数为 os.Args[1:]

fmt.Println("Parameters:", os.Args[1:])

可以通过标准库中 flag 库作为命令行入参的校验,其定义的变量类型是指针,获取对应值的时候需要添加引用 *

package main

import (
    "flag"
    "fmt"
)

var (
    name   = flag.String("name", "nick", "Input Your Name")
    age    = flag.Int("age", 28, "Input Your Age")
    gender = flag.String("gender", "male", "Input Your Gender")
)
var address string

func main() {
    flag.StringVar(&address, "address", "China", "Your Address")
    flag.Parse()

    fmt.Printf("args=%s, num=%d\n", flag.Args(), flag.NArg())
    for i := 0; i != flag.NArg(); i++ {
        fmt.Printf("arg[%d]=%s\n", i, flag.Arg(i))
    }

    fmt.Println("name=", *name)
    fmt.Println("age=", *age)
    fmt.Println("gender=", *gender)
    fmt.Println("address=", address)
}

在命令行中,可以使用如下的参数之一 -age=XXX -age XXX --age=XXX --age XXX

unsafe

这是一个很特殊的包,可以绕过 GoLang 本身的一些语法检查直接操作对象,从而可能会导致不可移植(可控),而且使用比较危险。该包中包含了三个函数,以及一种类型:

func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr

Pointer *ArbitraryType

上述函数类似于 C 中的宏,在编译时求值,而非运行时,也就是说它的结果可以分配给常量。

另外,uintptr 实际上是整型,其大小根据不同的平台变化,可以用来保存指针,例如在 32Bits 下是 4Bytes,在 64Bits 下是 8Bytes 。

使用示例

在官方文档中有介绍其常见的使用场景。

unsafe.Pointer VS. uintptr

uintptr 是一个可以容纳指针地址的整数类型,及时该变量仍然有效,但是其所指向地址的数据可能已经被 GC 回收掉。

unsafe.Pointer 是一个通用的指针类型,如果该变量有效,那么其指向的地址出的数据就不会被 GC 回收掉。

因为 uintptr 是一个整型,可以进行算术运算;那么就可以使用上述两者绕过限制操作变量,计算结构体中变量的偏移。

如下是两个示例,分别用来操作数组和结构体成员。

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    a := [4]int{0, 1, 2, 3}
    p1 := unsafe.Pointer(&a[1])
    p3 := unsafe.Pointer(uintptr(p1) + 2*unsafe.Sizeof(a[0]))
    *(*int)(p3) = 6
    fmt.Println("a =", a) // a = [0 1 2 6]

    type Person struct {
        name   string
        age    int
        gender bool
    }

    who := Person{"John", 30, true}
    pp := unsafe.Pointer(&who)
    pname := (*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.name)))
    page := (*int)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.age)))
    pgender := (*bool)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.gender)))
    *pname = "Alice"
    *page = 28
    *pgender = false
    fmt.Println(who) // {Alice 28 false}
}

signal

关于信号处理主要在 os/signal 中实现,其中包含了两个主要的方法:

  • Notify() 监听收到的信号
  • Stop() 取消监听
func Notify(c chan<- os.Signal, sig os.Signal)

简单使用用例。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    c := make(chan os.Signal)
    //signal.Notify(c) // default all signal
    signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGUSR1)
    fmt.Println("Waiting signal")

    s := <-c
    fmt.Println("Quit with", s)
}

最经常使用的用例如下,可以通过捕获信号来做一些清理操作。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    c := make(chan os.Signal, 128)
    signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM,
        syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

    go func() {
        for s := range c {
            switch s {
            case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
                fmt.Println("Quit with signal", s)
                os.Exit(0)
            case syscall.SIGUSR1:
                fmt.Println("USR1", s)
            case syscall.SIGUSR2:
                fmt.Println("USR2", s)
            default:
                fmt.Println("Other", s)
            }
        }
    }()

    fmt.Println("Starting ...")
    for {
        fmt.Println("Waiting ...")
        time.Sleep(time.Second)
    }
}

container

在官方代码 container 目录下包含了三个容器类型的数据类型,也就是 heaplistring,这里简单介绍其使用方式。

List

这里实现的是一个双向链表,其对应类型的定义如下。

type List struct {
	root Element
	len  int
}

type Element struct {
	next, prev *Element
	list *List
	Value interface{}
}

注意,这里定义的是类型而非接口。其中的示例以及接口可以直接参考 golang.org/pkg/container/list 文档中的相关介绍,如下是部分相关的示例整理。

package main

import (
	"container/list"
	"fmt"
)

func dump(l *list.List) {
	fmt.Printf("Total %d\n", l.Len())
	for e := l.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}
}

func reverse(l *list.List) {
	fmt.Printf("Total %d\n", l.Len())
	for e := l.Back(); e != nil; e = e.Prev() {
		fmt.Println(e.Value)
	}
}

func main() {
	l0 := list.New() // l0 := new(list.List).Init()
	e4 := l0.PushBack(4)
	e1 := l0.PushFront(1)
	l0.InsertAfter(2, e1)
	l0.InsertBefore(3, e4)

	l0.MoveToBack(e1)     // 2 3 4 1
	l0.MoveToFront(e4)    // 4 2 3 1
	l0.MoveAfter(e1, e4)  // 4 1 2 3
	l0.MoveBefore(e1, e4) // 1 4 2 3
	l0.Remove(e4)         // 1 2 3

	l2 := list.New()
	l2.PushBack(4)
	l3 := list.New()
	l3.PushFront(0)
	l0.PushBackList(l2)  // 1 2 3 4
	l0.PushFrontList(l3) // 0 1 2 3 4
	dump(l0)
}

Heap

这里的堆使用的数据结构是最小二叉树,即根节点比左边子树和右边子树的所有值都小,其对应的接口定义为:

type Interface interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

type Interface interface {
	sort.Interface
	Push(x interface{}) // add x as element Len()
	Pop() interface{}   // remove and return element Len() - 1.
}

也就是说,堆的接口继承自 sort.Interface 对应了 sort/sort.go 中的实现,那么对于堆来说,总共包含了上述的五个接口实现,使用时需要实现对应的接口才行。

package main

import (
	"container/heap"
	"fmt"
)

type IntHeap []int

func (h IntHeap) Len() int {
	return len(h)
}

func (h IntHeap) Less(i, j int) bool {
	return h[i] < h[j] // MinHeap
	//return h[i] > h[j] // MaxHeap
}

func (h IntHeap) Swap(i, j int) {
	h[i], h[j] = h[j], h[i]
}

func (h *IntHeap) Push(x any) {
	*h = append(*h, x.(int))
}

func (h *IntHeap) Pop() any {
	old, n := *h, len(*h)
	x := old[n-1]
	*h = old[:n-1]
	return x
}

func drain(h *IntHeap) {
	for h.Len() > 0 {
		fmt.Println(heap.Pop(h))
	}
}

func main() {
	h := &IntHeap{2, 1, 5}
	heap.Init(h)
	heap.Push(h, 3)
	(*h)[1] = 20
	heap.Fix(h, 1)
	heap.Remove(h, 3)
	drain(h)
}

也可以针对某个结构体进行封装,注意,结构体中的 index 用于执行 Fix() 时调用,如果不需要该功能,或者可以确定则忽略。

package main

import (
	"container/heap"
	"fmt"
)

type Item struct {
	value    string
	priority int
	index    int
}

func (i *Item) String() string {
	return i.value
}

type PQueue []*Item

func (p PQueue) Len() int {
	return len(p)
}

func (p PQueue) Less(i, j int) bool {
	return p[i].priority < p[j].priority // MinHeap
}

func (p PQueue) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
	p[i].index = i
	p[j].index = j
}

func (p *PQueue) Push(x any) {
	n, item := len(*p), x.(*Item)
	item.index = n - 1
	*p = append(*p, item)
}

func (p *PQueue) Pop() any {
	old, n := *p, len(*p)
	x := old[n-1]
	old[n-1] = nil
	x.index = -1
	*p = old[:n-1]
	return x.value
}

func drain(p *PQueue) {
	for p.Len() > 0 {
		fmt.Println(heap.Pop(p))
	}
}

func main() {
	h := &PQueue{&Item{
		value:    "banana",
		priority: 3,
		index:    0,
	}, &Item{
		value:    "apple",
		priority: 2,
		index:    1,
	}, &Item{
		value:    "pear",
		priority: 4,
		index:    2,
	}}
	heap.Init(h)
	heap.Push(h, &Item{
		value:    "orange",
		priority: 1,
	})

	item := (*h)[1] // apple
	item.priority = 10
	heap.Fix(h, item.index)

	// 这里是删除某个元素,不一定是按照优先级删除
	heap.Remove(h, 2)
	drain(h)
}

Ring

环也是链表实现,只是其尾部就是头部,所以每个元素实际上就可以代表自身的这个环,而不需要像 List 那样维护两个数据结构。

type Ring struct {
	next, prev *Ring
	Value      interface{}
}

初始化的时候,需要先定义好环的大小,然后可以对其每个元素进行操作。同时还提供一个 Do() 函数,能遍历一遍环,对每个元素执行次函数调用。

package main

import (
	"container/ring"
	"fmt"
)

func main() {
	r0 := ring.New(5)
	for i := 0; i < 5; i++ {
		r0.Value = i
		r0 = r0.Next() // r0.Prev()
	}

	r1 := ring.New(3)
	for i := 5; i < 8; i++ {
		r1.Value = i
		r1 = r1.Next() // r1.Prev()
	}
	r0.Link(r1)

	fmt.Printf("Total %d\n", r0.Len())
	r0.Do(func(v interface{}) {
		fmt.Println(v)
	})
}

sort

有三种场景可以使用。

基础类型

也就是 int float64 string 三类。

package main

import (
	"fmt"
	"sort"
)

func main() {
	intl := []int{9, 2, 7, 4}
	sort.Ints(intl)
	fmt.Println(intl)

	floatl := []float64{9.3, 2.4, 7.1, 4.3}
	sort.Float64s(floatl)
	fmt.Println(floatl)

	stringl := []string{"orange", "pear", "apple", "banana"}
	sort.Strings(stringl)
	fmt.Println(stringl)
}

上述是升序,也可以修改为降序。

sort.Sort(sort.Reverse(sort.IntSlice(intl)))
sort.Sort(sort.Reverse(sort.Float64Slice(floatl)))
sort.Sort(sort.Reverse(sort.StringSlice(stringl)))

其中 IntSlice 是对 []int 类型的封装,同时实现了 sort.Interface 定义的排序接口,这样就意味着升序可以通过 sort.Sort(sort.IntSclice(intl)) 实现;而 sort.Reverse 则是重新定义 Less 接口,采用倒序方式比较。

自定义比较

对于切片通过简单函数实现。

package main

import (
	"fmt"
	"sort"
	"strings"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	family := []Person{
		{"David", 5},
		{"Alice", 28},
		{"Andy", 5},
		{"Bob", 32},
	}

	// 按照Age升序排序,如果相同则保持原顺序
	sort.SliceStable(family, func(i, j int) bool {
		return family[i].Age < family[j].Age
	})
	fmt.Println(family)

    // 先按照Age升序排序,如果相同则按照Name升序排序
	sort.SliceStable(family, func(i, j int) bool {
		if family[i].Age != family[j].Age {
			return family[i].Age < family[j].Age
		}
		return strings.Compare(family[i].Name, family[j].Name) < 0
	})
	fmt.Println(family)
}

也可以通过 sort.Slice() 实现非稳定排序。

结构体

通过别名实现 sort.Interface 接口。

package main

import (
	"fmt"
	"sort"
)

type Person struct {
	Name string
	Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
	family := []Person{
		{"David", 5},
		{"Alice", 28},
		{"Andy", 5},
		{"Bob", 32},
	}

	sort.Sort(ByAge(family))
	fmt.Println(family)
}

也可以通过如下方式定义。

package main

import (
	"fmt"
	"sort"
)

type Person struct {
	Name string
	Age  int
}

func (p *Person) String() string {
	return fmt.Sprintf("%s/%d", p.Name, p.Age)
}

type Family struct {
	person []*Person
	less   func(x, y *Person) bool
}

func (f Family) Len() int           { return len(f.person) }
func (f Family) Less(i, j int) bool { return f.less(f.person[i], f.person[j]) }
func (f Family) Swap(i, j int)      { f.person[i], f.person[j] = f.person[j], f.person[i] }

func main() {
	person := []*Person{
		{"David", 5},
		{"Alice", 28},
		{"Andy", 5},
		{"Bob", 32},
	}
	family := Family{person, func(x, y *Person) bool {
		if x.Age != y.Age {
			return x.Age < y.Age
		}
		return x.Name < y.Name
	}}
	sort.Sort(family)
	fmt.Println(family.person)
}

查找

对于基础类型的切片可以通过如下方式查找。

package main

import (
	"fmt"
	"sort"
)

func main() {
	intl := []int{9, 2, 7, 4}
	sort.Ints(intl)
	fmt.Println(sort.IntsAreSorted(intl))
	fmt.Println(sort.SearchInts(intl, 4))
}

这里的 SearchInts() 函数实际上是对 Search() 的封装。

func SearchInts(a []int, x int) int {
	return Search(len(a), func(i int) bool { return a[i] >= x })
}

上述返回的是第一个大于等于 x 的索引,也就是 LowerBound 实现,如下是其它的实现。

// 查找第一个大于 x 的索引,也就是 UpperBound
sort.Search(len(a), func(i int) bool {
    return a[i] > x
})

// 查找第一个小于 x 的索引
sort.Search(len(a), func(i int) bool {
    return a[i] < x
})

// 查找第一个小于等于 x 的索引
sort.Search(len(a), func(i int) bool {
    return a[i] <= x
})

其中 Search 函数实现了二分搜索,如参实际上是个简单的闭包,在源码文档中甚至有个猜数字的游戏。

func GuessingGame() {
	var s string
	fmt.Printf("Pick an integer from 0 to 100.\n")
	answer := sort.Search(100, func(i int) bool {
		fmt.Printf("Is your number <= %d? ", i)
		fmt.Scanf("%s", &s)
		return s != "" && s[0] == 'y'
	})
	fmt.Printf("Your number is %d.\n", answer)
}

exec

exec 用来执行命令,实际上是将 os.StartProcess() 进行包装使得它更容易映射到 stdinstdout

其源码在 os/exec/ 中实现,其中核心的结构体为 type Cmd struct ,另外很多的示例可以参考源码目录下的 example_test.go 文件。

type Cmd struct {
	Path         string       // 运行命令的路径,可以是绝对路径或者相对路径
	Args         []string      // 命令参数
	Env          []string        // 环境变量,为 nil 时使用当前进程的环境变量
	Dir          string       // 指定命令运行时的工作目录
	Stdin        io.Reader     // 标准输入,nil 则从os.DevNull读取
	Stdout       io.Writer       // 标准输出
	Stderr       io.Writer     // 标准错误输出,nil 重定向到os.DevNull设备中

	ExtraFiles   []*os.File   
	SysProcAttr  *syscall.SysProcAttr
	Process      *os.Process         // 对应os.Process中的实现
	ProcessState *os.ProcessState  // 一个进程退出时的信息,当调用Wait()或者Run()时会生成该对象
}

常用接口。

// 在环境变量中查找可执行的二进制文件,可以在绝对路径、相对路径下查找
func LookPath(file string) (string, error)

// 仅设置Cmd结构中的Path和Args参数,如果不含路径分隔符则会尝试通过LookPath查找
func Command(name string, arg ...string) *Cmd
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
package main

import (
    "bytes"
    "log"
    "os/exec"
    "strings"
)

func main() {
    cmd := exec.Command("tr", "a-z", "A-Z")
    cmd.Stdin = strings.NewReader("some input")

    var out bytes.Buffer
    cmd.Stdout = &out

    err := cmd.Run()
    if err != nil {
            log.Fatal(err)
    }
    log.Printf("Got ===> %q\n", out.String())
}
// 运行命令并同时返回标准输出和标准错误
func (c *Cmd) Output() ([]byte, error)
func (c *Cmd) CombinedOutput() ([]byte, error)

如果执行命令出错会同时设置 error 错误。

package main

import (
    "log"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls", "-alh", "/")
    out, err := cmd.CombinedOutput()
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Got ===>%s", string(out))
}
// 开始执行命令,如果需要,应该使用Wait()等待命令执行完毕
func (c *Cmd) Start() error
// 执行指定的命令并且等待执行结束,实际上是Start()+Wait()的组合
func (c *Cmd) Run() error
package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls")
    cmd.Stdout = os.Stdout
    cmd.Run()
    fmt.Println(cmd.Start()) //exec: already started
}

注意,一个 Command 只能使用 Start() 或者 Run() 中的一个启动命令,不能两个同时使用。

// 等待命令退出,必须和Start一起使用
func (c *Cmd) Wait() error
// 将标准输入、输出等重定向,返回一个Pipe,在命令退出时会关闭这些Pipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command("cat")

    stdin, err := cmd.StdinPipe()
    if err != nil {
        fmt.Println(err)
    }
    _, err = stdin.Write([]byte("Hi World!!!\n"))
    if err != nil {
        fmt.Println(err)
    }
    stdin.Close()

    cmd.Stdout = os.Stdout
    cmd.Start()
}


package main

import (
    "fmt"
    "io/ioutil"
    "os/exec"
)

func main() {
    cmd := exec.Command("ls", "-alh")
    stdout, err := cmd.StdoutPipe()
    cmd.Start()
    content, err := ioutil.ReadAll(stdout)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(content))
}

os 包有一个 StartProcess() 可以用来调用或启动外部系统命令和二进制可执行文件,三个入参分别为:A) 运行的进程;B) 传递选项或参数;C) 系统环境基本信息的结构体。

package main

import (
    "fmt"
    "os"
)

func main() {
    proc, err := os.StartProcess(
        "/bin/ls",
        []string{"ls", "-l"},
        &os.ProcAttr{
            Env: os.Environ(),
            Files: []*os.File{
                os.Stdin,
                os.Stdout,
                os.Stderr,
            },
        })
    if err != nil {
        fmt.Printf("Error %v starting process!", err)
        os.Exit(1)
    }
    fmt.Printf("The process id is %v\n", proc)

    _, err = proc.Wait()
    if err != nil {
        fmt.Printf("Error %v wait process!", err)
        os.Exit(1)
    }

}

进程管理

package main

import (
    "errors"
    "fmt"
    "os"
    "os/exec"
    "sync"
    "syscall"
    "time"
)

type procInfo struct {
    cmdset  []string
    cmd     *exec.Cmd
    mu      sync.Mutex
    waitErr error
    cond    *sync.Cond
}

var procs map[string]*procInfo
var wg sync.WaitGroup

func init() {
    procs = make(map[string]*procInfo)

    procs["FOOBAR"] = &procInfo{
        cmdset: []string{"/bin/sh", "-c", "sleep 1000"},
    }

    for k, v := range procs {
        procs[k].cond = sync.NewCond(&v.mu)
    }
}

// spawn command that specified as proc.
func spawnProc(proc string) error {
    p := procs[proc]

    cmd := exec.Command(p.cmdset[0], p.cmdset[1:]...)
    cmd.Stdin = nil
    cmd.Stdout = nil // TODO: some log
    cmd.Stderr = nil
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    cmd.Env = os.Environ()
    //cmd.Env = append(os.Environ(), fmt.Sprintf("PORT=%d", 1234))

    fmt.Printf("Starting %s\n", proc)

    err := cmd.Start()
    if err != nil {
        fmt.Printf("Failed to start %s: %s\n", proc, err)
        return err
    }
    p.cmd = cmd
    p.mu.Unlock()

    err = cmd.Wait()

    p.mu.Lock()
    p.cond.Broadcast()
    p.waitErr = err
    p.cmd = nil
    fmt.Printf("Terminating %s\n", proc)
    return nil
}

func terminateProc(proc string, signal os.Signal) error {
    p := procs[proc].cmd.Process
    if p == nil {
        return nil
    }

    pgid, err := syscall.Getpgid(p.Pid)
    if err != nil {
        return err
    }

    // use pgid, ref: http://unix.stackexchange.com/questions/14815/process-descendants
    pid := p.Pid
    if pgid == p.Pid {
        pid = -1 * pid
    }

    target, err := os.FindProcess(pid)
    if err != nil {
        return err
    }
    return target.Signal(signal)
}

func startProc(proc string) error {
    p, ok := procs[proc]
    if !ok || p == nil {
        return errors.New("unknown proc: " + proc)
    }

    p.mu.Lock()
    if procs[proc].cmd != nil { /* already running */
        p.mu.Unlock()
        return nil
    }

    wg.Add(1)
    go func() {
        spawnProc(proc)
        wg.Done()
        p.mu.Unlock()
    }()
    return nil
}

func stopProc(proc string, signal os.Signal) error {
    if signal == nil {
        signal = syscall.SIGTERM
    }
    p, ok := procs[proc]
    if !ok || p == nil {
        return errors.New("unknown proc: " + proc)
    }

    p.mu.Lock()
    defer p.mu.Unlock()

    if p.cmd == nil { /* not start yet */
        return nil
    }

    err := terminateProc(proc, signal)
    if err != nil {
        return err
    }

    timeout := time.AfterFunc(10*time.Second, func() {
        p.mu.Lock()
        defer p.mu.Unlock()
        if p, ok := procs[proc]; ok && p.cmd != nil {
            err = p.cmd.Process.Kill()
        }
    })
    p.cond.Wait()
    timeout.Stop()

    return err
}

func main() {
    startProc("FOOBAR")

    stopProc("FOOBAR", nil)

    wg.Wait()
    fmt.Println("OK, All Done")
}

如上实际上是通过单个协程处理一个进程,也可以通过信号量来处理。

signalC := make(chan os.Signal, 128)
signal.Notify(signalC, syscall.SIGCHLD)
defer signal.Stop(signalC)

go func() {
	for s := range signalC {
		switch s {
		case syscall.SIGCHLD:
			for {
				var wstatus syscall.WaitStatus

				pid, err := syscall.Wait4(-1, &wstatus, syscall.WNOHANG, nil)
				if syscall.EINTR == err {
					continue
				} else if syscall.ECHILD == err {
					break
				}

				fmt.Printf("Reaper cleanup: pid=%d, wstatus=%+v\n",
					pid, wstatus)
			}

		default:
			fmt.Println("Other", s)
		}
	}
}()