简单介绍常见的模块,例如 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
,这里简单介绍其使用方式。
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
中的实现,那么对于堆来说,总共包含了上述的五个接口实现。
List
这里实现的是一个双向链表,其对应类型的定义如下。
type List struct {
root Element
len int
}
type Element struct {
next, prev *Element
list *List
Value interface{}
}
注意,这里定义的是类型而非接口。
其中的示例以及接口可以直接参考 golang.org/pkg/container/list 中的介绍。
Ring
环其实也是链表的实现,只是其尾部就是头部,所以每个元素实际上就可以代表自身的这个环,而不需要像 list 那样维护两个数据结构。
type Ring struct {
next, prev *Ring
Value interface{}
}
初始化的时候,需要先定义好环的大小,然后可以对其每个元素进行操作。同时还提供一个 Do()
函数,能遍历一遍环,对每个元素执行次函数调用。
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)
}
}
}()