进程管理以及进程间的通信是通过gproc模块实现的,其中进程间通信采用的是本地socket通信机制。

使用方式:

  1. import "gitee.com/johng/gf/g/os/gproc"

方法列表:godoc.org/github.com/johng-cn/gf/g/os/gproc

  1. func IsChild() bool
  2. func PPid() int
  3. func PPidOS() int
  4. func Pid() int
  5. func Send(pid int, data []byte, group ...string) error
  6. func SetPPid(ppid int)
  7. func Shell(cmd string, out io.Writer, in io.Reader) error
  8. func ShellExec(cmd string) (string, error)
  9. func ShellRun(cmd string) error
  10. func StartTime() time.Time
  11. func Uptime() int
  12. type Manager
  13. func NewManager() *Manager
  14. func (m *Manager) AddProcess(pid int)
  15. func (m *Manager) Clear()
  16. func (m *Manager) GetProcess(pid int) *Process
  17. func (m *Manager) KillAll() error
  18. func (m *Manager) NewProcess(path string, args []string, environment []string) *Process
  19. func (m *Manager) Pids() []int
  20. func (m *Manager) Processes() []*Process
  21. func (m *Manager) RemoveProcess(pid int)
  22. func (m *Manager) Send(data []byte)
  23. func (m *Manager) SendTo(pid int, data []byte) error
  24. func (m *Manager) SignalAll(sig os.Signal) error
  25. func (m *Manager) Size() int
  26. func (m *Manager) WaitAll()
  27. type Msg
  28. func Receive(group ...string) *Msg
  29. type Process
  30. func NewProcess(path string, args []string, environment ...[]string) *Process
  31. func (p *Process) Kill() error
  32. func (p *Process) Pid() int
  33. func (p *Process) Release() error
  34. func (p *Process) Run() error
  35. func (p *Process) Send(data []byte) error
  36. func (p *Process) Signal(sig os.Signal) error
  37. func (p *Process) Start() (int, error)

简要说明:

  1. Manager对象为进程管理对象,可以同时管理多个子进程(当前执行进程为父进程);
  2. Process为进程对象,表示特定执行或者获取的一个进程资源;
  3. 我们可以通过ShellShellExecShellRun来执行Shell指令:
    • Shell表示一个原生的Shell指令执行方式,带自定义的输入和输出控制;
    • ShellExec执行命令后将会返回输出的结果内容;
    • ShellRun执行命令后将会直接将返回内容输出到标准输出;
    • 我们可以使用goroutine来实现异步的执行,如:go ShellRun(...)等等;

由于进程管理及通信的内容比较多,以下对常用的几种使用做简单介绍。

进程管理

执行Shell命令

  1. package main
  2. import (
  3. "gitee.com/johng/gf/g/os/gproc"
  4. "fmt"
  5. )
  6. func main () {
  7. r, err := gproc.ShellExec(`sleep 3s; echo "hello gf!";`)
  8. fmt.Println("result:", r)
  9. fmt.Println(err)
  10. }

执行后,可以看到程序等待了3秒之后,输出结果为:

  1. result: hello gf!
  2. <nil>

主进程与子进程

gproc.Manager对象创建的进程都默认带子进程标识,在子进程程序中可以通过gproc.IsChild()方法来判断自身是否为子进程。

  1. package main
  2. import (
  3. "os"
  4. "time"
  5. "gitee.com/johng/gf/g/os/glog"
  6. "gitee.com/johng/gf/g/os/gproc"
  7. )
  8. func main () {
  9. if gproc.IsChild() {
  10. glog.Printfln("%d: Hi, I am child, waiting 3 seconds to die", gproc.Pid())
  11. time.Sleep(time.Second)
  12. glog.Printfln("%d: 1", gproc.Pid())
  13. time.Sleep(time.Second)
  14. glog.Printfln("%d: 2", gproc.Pid())
  15. time.Sleep(time.Second)
  16. glog.Printfln("%d: 3", gproc.Pid())
  17. } else {
  18. m := gproc.NewManager()
  19. p := m.NewProcess(os.Args[0], os.Args, os.Environ())
  20. p.Start()
  21. p.Wait()
  22. glog.Printfln("%d: child died", gproc.Pid())
  23. }
  24. }

执行后,终端打印结果如下:

  1. 2018-05-18 14:35:41.360 28285: Hi, I am child, waiting 3 seconds to die
  2. 2018-05-18 14:35:42.361 28285: 1
  3. 2018-05-18 14:35:43.361 28285: 2
  4. 2018-05-18 14:35:44.361 28285: 3
  5. 2018-05-18 14:35:44.362 28278: child died

多进程管理

gproc除了能够创建子进程,管理子进程之外,也能管理非自身创建的其他进程。gproc可以同时管理多个进程,这里以单个进程为例来演示对进程的管理功能。

  1. 我们使用gedit软件(Linux下常用的文本编辑器)随意打开一个文件,在进程当中我们看到该gedit的进程ID为28536
    1. $ ps aux | grep gedit
    2. john 28536 3.6 0.6 946208 56412 ? Sl 14:39 0:00 gedit /home/john/Documents/text
  2. 我们的程序如下:

    1. package main
    2. import (
    3. "fmt"
    4. "gitee.com/johng/gf/g/os/gproc"
    5. )
    6. func main () {
    7. pid := 28536
    8. m := gproc.NewManager()
    9. m.AddProcess(pid)
    10. m.KillAll()
    11. m.WaitAll()
    12. fmt.Printf("%d was killed\n", pid)
    13. }

    执行后,gedit被关闭,终端输出信息为:

    1. 28536 was killed

进程通信

不要通过共享内存来通信,而应该通过通信来共享内存。

常见的进程通信方式有5种:管道/信号量/共享内存/共享文件/Socket。按照常见的并发架构的设计来讲,我们尽可能地少用锁机制,包括共享内存/共享文件其实都是需要依靠锁机制才能保证数据流的正确性,因为锁机制带来的维护复杂度往往会比其带来的好处更多。信号量常用在*nix系统中,跨平台性比较差。管道虽然实现起来比较简单,但是在稳定性上并没有Socket机制好。因此,gproc实现的进程通信采用的是Socket机制。但是需要注意的是,通信的两个进程都需要使用gproc模块来实现发送&接收数据

gproc的进程通信API非常简便,只需通过以下两个方法实现:

  1. func Send(pid int, data []byte) error
  2. func Receive() *Msg

我们通过Send方法向指定的进程发送数据(每调用一次相当于发送一条消息),在指定的进程中可以通过Receive方法获得数据。其中,Receive方法提供了类似消息队列的形式来收取其他进程传递的数据,当队列为空时,该方法将会阻塞等待。

我们来看一个进程间通信的基本使用示例:

  1. package main
  2. import (
  3. "os"
  4. "fmt"
  5. "time"
  6. "gitee.com/johng/gf/g/os/gproc"
  7. "gitee.com/johng/gf/g/os/gtime"
  8. )
  9. func main () {
  10. fmt.Printf("%d: I am child? %v\n", gproc.Pid(), gproc.IsChild())
  11. if gproc.IsChild() {
  12. gtime.SetInterval(time.Second, func() bool {
  13. gproc.Send(gproc.PPid(), []byte(gtime.Datetime()))
  14. return true
  15. })
  16. select { }
  17. } else {
  18. m := gproc.NewManager()
  19. p := m.NewProcess(os.Args[0], os.Args, os.Environ())
  20. p.Start()
  21. for {
  22. msg := gproc.Receive()
  23. fmt.Printf("receive from %d, data: %s\n", msg.Pid, string(msg.Data))
  24. }
  25. }
  26. }

该示例中,我们的主进程启动时创建了一个子进程,该子进程每隔1秒钟向主进程发送当前的时间,主进程收取到子进程发送的参数后输出到终端上。执行后,终端输出的内容如下:

  1. 29978: I am child? false
  2. 29984: I am child? true
  3. receive from 29984, data: 2018-05-18 15:01:00
  4. receive from 29984, data: 2018-05-18 15:01:01
  5. receive from 29984, data: 2018-05-18 15:01:02
  6. receive from 29984, data: 2018-05-18 15:01:03
  7. receive from 29984, data: 2018-05-18 15:01:04
  8. ...