关闭通道`done`后,不要打印`shutdown msg`

时间:2017-08-08 14:53:20

标签: go concurrency

两个例程从同一频道读取。在done频道关闭后,第一个例程从不打印关闭消息,而第二个例程总是这样做。

为什么来自第一个例程的消息不打印,方法是returning

main.go

func main() {
done := make(chan bool)
c := make(chan os.Signal, 1)

cameras := client.CameraConfig()
client.DrawUserControls(cameras)

operator := client.NewOperator(cameras)
go operator.UserInputListener(done)
go operator.ParseAndExecuteUserCommand(done)

signal.Notify(c, os.Interrupt)
for range c {
    close(done)
    break
}

log.Println("Interrupt signal received. Shutting client down....")
time.Sleep(5 * time.Second)
}

client.go

func (o *Operator) UserInputListener(done <-chan bool) {
    reader := bufio.NewReader(os.Stdin)
    for {
        select {
        case <-done:
            log.Println("Keyboard listener shutting down.") // <-- this never prints
            return
        default:
            line, _, err := reader.ReadLine()
            if err != nil {
                log.Println(err)
            }

            data := strings.Split(string(line), "")

            id, err := strconv.Atoi(data[1])
            if err != nil {
                log.Println(err)
                continue
            }

            switch data[0] {
            case "b":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "run",
                }
            case "t":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "terminate",
                }
            case "r":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "record",
                }
            case "s":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "stop",
                }
            }
        }
    }
}

func (o *Operator) ParseAndExecuteUserCommand(done <-chan bool) {
    for {
        select {
        case <-done:
            log.Println("Command operator shutting down.")
            return
        case ctrl := <-o.Controls:
            switch ctrl.Ctrl {
            case "run":
                o.Room[ctrl.Identifier].Run()
            case "terminate":
                o.Room[ctrl.Identifier].Close()
            case "record":
                o.Room[ctrl.Identifier].Write()
            case "stop":
                o.Room[ctrl.Identifier].Stop()
            }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

原因是因为你已经创建了同步频道,你在这里推送了1条消息,然后你也只能阅读它一次。这是因为您只从done频道获得了1次(随机)阅读。

关闭goroutine的方法是使用WaitGroup
main.go:

var (
    done            chan bool
)

func main() {
    cameras := client.CameraConfig()
    client.DrawUserControls(cameras)
    operator := client.NewOperator(cameras)

    done = make(chan bool, 1)
    wg := &sync.WaitGroup{}
    wg.Add(2)
    go operator.UserInputListener(done, wg)
    go operator.ParseAndExecuteUserCommand(done, wg)

    handleShutdown()
    wg.Wait()
}


func handleShutdown() {
    ch := make(chan os.Signal, 1)
    go func() {
        <-ch //wait for application terminating
        log.Println("Shutdown received.")
        close(done)
    }()
    signal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
}

client.go:

func (o *Operator) UserInputListener(done <-chan bool, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-done:
            log.Println("Keyboard listener shutting down.") 
            return
        ........
        }
    }
}

func (o *Operator) ParseAndExecuteUserCommand(done <-chan bool, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-done:
            log.Println("Command operator shutting down.") 
            return
        ........
        }
    }
}

使用此link了解详情

答案 1 :(得分:0)

UserInputListener未进入case <-done:的原因是因为它等待此行的某些输入而停滞不前:

line, _, err := reader.ReadLine()

此行正在阻止!

此类问题的解决方案是什么?

这并不容易。

阅读例程

您可以使用另一个例行程序进行阅读,将数据发送到您从done频道中读取的选择中读取的频道。这将正确关闭UserInputListener,但让另一个goroutine不能正常关闭。但也许这并不重要......?

func (o *Operator) UserInputListener(done <-chan bool) {
    // channel with some buffer so reader doesn't have to wait (so much)
    ch := make(chan string, 10)
    go func() {
        reader := bufio.NewReader(os.Stdin)
        for {
            line, _, err := reader.ReadLine()
            if err != nil {
                log.Println(err)
                // stop on error?
                // return
            }
            ch <- string(line)
        }
    }()

    for {
        select {
        case <-done:
            log.Println("Keyboard listener shutting down.") // <-- this never prints
            return
        case line:= <-ch:
            data := strings.Split(line, "")

            id, err := strconv.Atoi(data[1])
            if err != nil {
                log.Println(err)
                continue
            }

            switch data[0] {
            case "b":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "run",
                }
            case "t":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "terminate",
                }
            case "r":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "record",
                }
            case "s":
                o.Controls <- Ctrl{
                    Identifier: id,
                    Ctrl:       "stop",
                }
            }
        }
    }
}

关闭读者来源

您也可以尝试关闭reader正在读取的内容。我已经在其他环境中使用过此解决方案,例如从串行设备读取。

这不适用于os.Stdin,但正如JimB指出的那样。 os.StdIn.Close()会在等待读者完成时阻止。