简单介绍常见的模块,例如 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
目录下包含了三个容器类型的数据类型,也就是 heap
、list
和 ring
,这里简单介绍其使用方式。
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()
进行包装使得它更容易映射到 stdin
和 stdout
。
其源码在 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)
}
}
}()