Go語言學習之signal(二)

package main

import (
    "bytes"
    "errors"
    "fmt"
    "io"
    "os"
    "os/exec"
    "os/signal"
    "runtime/debug"
    "strconv"
    "strings"
    "sync"
    "syscall"
    "time"
)

func main() {
    go func() {
        time.Sleep(5 * time.Second)
        sendSignal()
    }()
    handleSignal()
}

func handleSignal() {
/*
    這裏先後調用了兩次signal.Notify函數,
    第一次調用時設定的信號集合中包含SIGINT,SINGQUIT兩個信號
    第二次調用時只設定了SIGQUIT一個信號

    如果當前進程接收的時SIGQUIT信號,則分別發送給sigRecv1和sigRecv2兩個通道
    如果接收到的時SIGINT信號,則只發送給sigRecv1這一個通道
*/
    sigRecv1 := make(chan os.Signal, 1)
    sigs1 := []os.Signal{syscall.SIGILL, syscall.SIGQUIT}
    fmt.Printf("Set notfication for %s...[sigRecv1]\n", sigs1)
    signal.Notify(sigRecv1, sigs1...)
    sigRecv2 := make(chan os.Signal, 1)
    sigs2 := []os.Signal{syscall.SIGQUIT}
    fmt.Printf("Set notification for %s...[sigRecv2]\n", sigs2)
    signal.Notify(sigRecv2, sigs2...)

/*
    sync,WaitGroup類型值wg的Add方法。添加了一個類型值爲2的差量,然後在每段併發程序後面,
    分別調用了wg的Done方法,該發放可以視爲使差量減1, 在最後,還應該調用wg的Wait方法,該方法
    會一直阻塞, 知道差量變爲0
*/
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        for sig := range sigRecv1 {
            fmt.Printf("Received a signal from sigRecv1: %s\n", sig)

        }
        fmt.Printf("End. [sigRecv1]\n")
        wg.Done()
    }()
    go func() {
        for sig := range sigRecv2 {
            fmt.Printf("Received a signal from sigRecv2: %s\n", sig)
        }
        fmt.Printf("End. [sigRecv2]\n")
        wg.Done()
    }()
    fmt.Println("Wait for 2 seconds... ")
    time.Sleep(2 * time.Second)
    fmt.Printf("Stop notification...")
    signal.Stop(sigRecv1)
    close(sigRecv1)
    fmt.Printf("done. [SigRecv1]\n")
    wg.Wait()
}

func sendSignal() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Printf("Fatal Error: %s\n", err)
            debug.PrintStack()
        }
    }()

    cmds := []*exec.Cmd{
        exec.Command("ps", "aux"),
        exec.Command("grep", "signal"),
        exec.Command("grep", "-v", "grep"),
        exec.Command("grep", "-v", "go run"),
        exec.Command("awk", "{print $2}"),
    }
    output, err := runCmds(cmds)
    if err != nil {
        fmt.Printf("Command Execution Error:", err)
        return
    }
    pids, err := getPids(output)
    if err != nil {
        fmt.Printf("PID Parsing Error: %s\n", err)
        return
    }

    fmt.Printf("Target PID(s):\n%v\n", pids)
    for _, pid := range pids {
        proc, err := os.FindProcess(pid)
        if err != nil {
            fmt.Printf("Process Finding Error: %s\n", err)
            return
        }
        sig := syscall.SIGQUIT
        fmt.Printf("Send signal '%s' to the process (pid=%d)...\n", sig, pid)
        err = proc.Signal(sig)
        if err != nil {
            fmt.Printf("Signal Sending Error:%s\n", err)
            return
        }

    }
}

func getPids(strs []string) ([]int, error) {
    var pids []int
    for _, str := range strs {
        pid, err := strconv.Atoi(strings.TrimSpace(str))
        if err != nil {
            return nil, err
        }
        pids = append(pids, pid)
    }
    return pids, nil
}

func runCmds(cmds []*exec.Cmd) ([]string, error) {
    if cmds == nil || len(cmds) == 0 {
        return nil, errors.New("The cmd slice is incvalid!")
    }
    first := true
    var output []byte
    var err error
    for _, cmd := range cmds {
        fmt.Printf("Run command:%v\n", getCmdPlaintext(cmd))
        if !first {
            var stdinBuf bytes.Buffer
            stdinBuf.Write(output)
            cmd.Stdin = &stdinBuf
        }
        var stdoutBuf bytes.Buffer
        cmd.Stdout = &stdoutBuf
        if err = cmd.Start(); err != nil {
            return nil, getError(err, cmd)
        }
        if err = cmd.Wait(); err != nil {
            return nil, getError(err, cmd)
        }
        output = stdoutBuf.Bytes()
        fmt.Printf("Output: \n%s\n", string(output))
        if first {
            first = false
        }
    }
    var lines []string
    var outputBuf bytes.Buffer
    outputBuf.Write(output)
    for {
        line, err := outputBuf.ReadBytes('\n')
        if err != nil {
            if err == io.EOF {
                break
            } else {
                return nil, getError(err, nil)
            }
        }
        lines = append(lines, string(line))
    }
    return lines, nil
}
func getCmdPlaintext(cmd *exec.Cmd) string {
    var buf bytes.Buffer
    buf.WriteString(cmd.Path)
    for _, arg := range cmd.Args[1:] {
        buf.WriteRune(' ')
        buf.WriteString(arg)
    }
    return buf.String()
}

func getError(err error, cmd *exec.Cmd, extraInfo ...string) error {
    var errMsg string
    if cmd != nil {
        errMsg = fmt.Sprintf("%s [%s %v]", err, (*cmd).Path, (*cmd).Args)

    } else {
        errMsg = fmt.Sprintf("%s", err)
    }
    if len(extraInfo) > 0 {
        errMsg = fmt.Sprintf("%s (%v)", errMsg, extraInfo)
    }
    return errors.New(errMsg)
}

代碼包os/signal中Notify函數用來當操作系統向當前進程發送指定信號時發出通知

func Notify(c chan<-os.Signal, sig ...os.Signal)

其中第一個參數是通道類型的,該參數的類型的chan<-os.Signal,這表示參數c是一個發送通道,在Notify函數中,只能向它發送os.Signal類型的值(以下簡稱信號值),而不能從中接收信號值。signal.Notify函數會把當前進程中接收到的指定信號放入參數c代表的通道類型值中,這樣該函數的調用方就可以從這個signal接收通道中按順序獲取操作系統發來的信號並進行相應的處理。

第二個參數是可變長的參數,這意味着我們在調用signal.Notify函數時,可以在第一個參數值之後再附加任意個os.Signal類型的值,os/signal包中的程序會把它封裝成syscall.Signal類型的值並放入到signal接收通道中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章