go routine在关闭通道`done`后没有打印`shutdown msg`。

huangapple go评论91阅读模式
英文:

go routine not printing `shutdown msg` after closing channel `done`

问题

两个Go协程从同一个通道读取。第一个Go协程在done通道关闭后从未打印其关闭消息,而第二个Go协程总是打印。

为什么第一个Go协程的消息没有打印出来,而且这个方法是否在return

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()
            }
        }
    }
}
英文:

Two go routines reading from the same channel. The first go routine never prints its shutdown message after the done channel is closed, while the second go routine always does.

Why is the message from the first go routine not printing and is the method even 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(&quot;Interrupt signal received. Shutting client down....&quot;)
time.Sleep(5 * time.Second)
}

client.go

func (o *Operator) UserInputListener(done &lt;-chan bool) {
reader := bufio.NewReader(os.Stdin)
for {
select {
case &lt;-done:
log.Println(&quot;Keyboard listener shutting down.&quot;) // &lt;-- this never prints
return
default:
line, _, err := reader.ReadLine()
if err != nil {
log.Println(err)
}
data := strings.Split(string(line), &quot;&quot;)
id, err := strconv.Atoi(data[1])
if err != nil {
log.Println(err)
continue
}
switch data[0] {
case &quot;b&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;run&quot;,
}
case &quot;t&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;terminate&quot;,
}
case &quot;r&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;record&quot;,
}
case &quot;s&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;stop&quot;,
}
}
}
}
}
func (o *Operator) ParseAndExecuteUserCommand(done &lt;-chan bool) {
for {
select {
case &lt;-done:
log.Println(&quot;Command operator shutting down.&quot;)
return
case ctrl := &lt;-o.Controls:
switch ctrl.Ctrl {
case &quot;run&quot;:
o.Room[ctrl.Identifier].Run()
case &quot;terminate&quot;:
o.Room[ctrl.Identifier].Close()
case &quot;record&quot;:
o.Room[ctrl.Identifier].Write()
case &quot;stop&quot;:
o.Room[ctrl.Identifier].Stop()
}
}
}
}

答案1

得分: 2

原因是因为你创建了同步通道,并在这里推送了1条消息,然后你只能读取一次。这是因为你只从done通道中获取1次(随机)读取。

你可以使用WaitGroup来关闭goroutine:
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 //等待应用程序终止
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
........
}
}
}

使用此链接获取详细信息

英文:

The reason is because you have created synchronous channel and you push here 1 message and then you could read it only once as well. That is because you get only 1 (random) read from done channel.

The way you can shut down your goroutines is to use 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 := &amp;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() {
&lt;-ch //wait for application terminating
log.Println(&quot;Shutdown received.&quot;)
close(done)
}()
signal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
}

client.go:

func (o *Operator) UserInputListener(done &lt;-chan bool, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case &lt;-done:
log.Println(&quot;Keyboard listener shutting down.&quot;) 
return
........
}
}
}
func (o *Operator) ParseAndExecuteUserCommand(done &lt;-chan bool, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case &lt;-done:
log.Println(&quot;Command operator shutting down.&quot;) 
return
........
}
}
}

Use this link for details

1: http://www.hydrogen18.com/blog/stopping-it-all-in-go.html "link"

答案2

得分: 0

UserInputListener无法进入case <-done:的原因是它在以下代码行中被阻塞等待输入:

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

这一行是阻塞的!

这种问题的解决方案是什么?

这并不容易。

读取例程

你可以使用另一个go例程来进行读取,将数据发送到一个通道中,在select语句中从该通道读取数据,同时也从done通道中读取数据。这样可以正确关闭UserInputListener,但是可能无法正确关闭其他的goroutine。但也许这并不重要...?

func (o *Operator) UserInputListener(done <-chan bool) {
    // 带有一定缓冲区的通道,这样读取器就不必等待(太多)
    ch := make(chan string, 10)
    go func() {
        reader := bufio.NewReader(os.Stdin)
        for {
            line, _, err := reader.ReadLine()
            if err != nil {
                log.Println(err)
                // 在错误时停止?
                // return
            }
            ch <- string(line)
        }
    }()
    
    for {
        select {
        case <-done:
            log.Println("键盘监听器关闭。") // <-- 这句话永远不会打印
            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()会阻塞,因为它等待读取器完成。

英文:

The reason UserInputListener is not entering case &lt;-done: is because it is stuck waiting for some input at this line:

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

This line is blocking!

What is the solution for this type of problem?

This is not easy.

Read Routine

You could use another go routine to do the reading, send the data into a channel that you read from in the select where you also read from the done channel. That would properly close the UserInputListener but leave the other goroutine to not be closed properly. But maybe that does not matter as much...?

func (o *Operator) UserInputListener(done &lt;-chan bool) {
// channel with some buffer so reader doesn&#39;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 &lt;- string(line)
}
}()
for {
select {
case &lt;-done:
log.Println(&quot;Keyboard listener shutting down.&quot;) // &lt;-- this never prints
return
case line:= &lt;-ch:
data := strings.Split(line, &quot;&quot;)
id, err := strconv.Atoi(data[1])
if err != nil {
log.Println(err)
continue
}
switch data[0] {
case &quot;b&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;run&quot;,
}
case &quot;t&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;terminate&quot;,
}
case &quot;r&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;record&quot;,
}
case &quot;s&quot;:
o.Controls &lt;- Ctrl{
Identifier: id,
Ctrl:       &quot;stop&quot;,
}
}
}
}
}

Close Reader Source

You could also try to close whatever the reader is reading from. I have already used this solution in other contexts like reading from serial devices.

This does not work with os.Stdin however as JimB pointed out to me. os.StdIn.Close() will block as it waits for the reader to finish.

huangapple
  • 本文由 发表于 2017年8月8日 22:53:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/45571628.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定