【问题标题】:How to Close the goroutine which waiting on I/O如何关闭等待 I/O 的 goroutine
【发布时间】:2015-05-30 16:16:21
【问题描述】:

我创建了两个goroutinesenderreceiver,sender会不断的从用户(键盘)获取数据并写入stream,receiver会独立获取值从流中将其打印到屏幕上。两者都使用 goroutine 并发

在某个时间点,receiver 失败并关闭连接并退出receiver goroutine,但sender goroutine 等待用户输入(i/o 操作)不会被关闭。在这种情况下如何退出所有的 goroutine?

以下是此场景的示例代码。

package main

import (
    "fmt"
    "time"
)

var stop bool = false

func sender() {
    str := ""
    for !stop {
        fmt.Scanf("%s", &str)
        fmt.Println("Entered :", str)
    }   
    fmt.Println("Closing sender goroutine")
}

func receiver() {
    i := 0
    for !stop {
        i++
        if i > 5 { 
            stop = true
        }
        time.Sleep(1 * time.Second)
    }   
    fmt.Println("Closing receiver goroutine")
}

func main() {
    go sender()
    go receiver()

    /* Wait for goroutines to finish */
    for !stop {
        time.Sleep(1 * time.Millisecond)
    }   
    time.Sleep(1 * time.Second)

    panic("Display stack")
}

上述代码发送方将在 5 次循环后等待用户输入接收方将退出接收方 go 例程。 I expect when receiver close, go routine which waiting on i/o has to be closed.

请帮我解答这个问题。

【问题讨论】:

  • 旁注:同时读写同一个变量 (stop) 是不安全的。不要永远这样做。这是一场经典的数据竞赛,拥有completely undefined behaviour。 Go 的race detector 可以为您指出这些。
  • 在这个例子中你实际上并没有发送任何东西。但是,如果您只是想要原始数据(字符串、字节),那么可能io.Pipe 可能会方便双方执行defer myside.Close()(其中myside 是管道的读取端或写入端,视情况而定) .在一般情况下,您会使用一个通道来发送数据,并且编写器可以关闭它以表示它已完成;但读者不应该/不能关闭;这通常意味着您需要一个单独的通道或其他同步机制来通知编写器退出。

标签: concurrency go goroutine


【解决方案1】:

正如 Dave C 和 JimB 所说,使用通道来协调 goroutine。这是一个可能有帮助的例子。

收到用户的5条消息后退出:

package main

import "fmt"

var pipe = make(chan string) //shares text entered by user
var stop = make(chan bool)   //shares stop signal

func listen() {
    for {
        var input string
        fmt.Scan(&input)
        pipe <- input
    }
}

func write() {
    for i := 0; i < 5; i++ {
        var output string
        output = <-pipe
        fmt.Println("Received", output)
    }

    stop <- true
}

func main() {
    go listen()
    go write()

    <-stop
}

【讨论】:

  • 在您的示例代码中,listen() go 例程在 write() go 例程 5 次写入后不会退出,因为它会留在 for 循环本身中,这可能是泄漏。只有 main() 和 write() 会正确退出。在 main 的末尾使用 panic 语句并检查堆栈跟踪。这种情况的预期解决方案
  • 我同意 JimB 的观点;如果无法避免扫描,最容易让 goroutine 运行。但也许这个pipe techniquethis thread 有帮助?
【解决方案2】:

首先,您的代码围绕stop 变量展开竞争。当存在数据竞争时,无法保证您的程序将按照定义运行。使用通道来同步 goroutine。然而,这并不是您继续编程的原因。

您的代码在fmt.Scanf 上阻塞,无法检查stop 条件。由于无法中断对 Stdin 的读取(发生在 fmt.Scanf 内部),您需要在再次调用 Scanf 之前检查停止条件。如果没有更多输入,但您在 Stdin 上有一个未决的读取,处理它的最简单方法是让该 goroutine 运行。有一些相当复杂的方法可以使用称为“self-pipe”的技巧来解决这个问题,但通常不值得付出努力,因为 goroutine 很小并且不会占用很多资源。

for {
    fmt.Scanf("%s", &str)
    fmt.Println("Entered :", str)
    // use a channel or context to detect when to exit
    select {
    case <-ctx.Done():
        return
    default:
    }
}

【讨论】:

  • 感谢您的回复。在 fmt.Scanf 执行后的解决方案中,只有执行才会选择并返回,当 fmt.Scanf 没有输入时我的问题是如何关闭 go 例程。
  • @sujin:如答案中所述,您不能直接中断 stdin 的读取(这不是 Go 问题,在 POSIX 中,您不能从另一个线程中断 read )。您可以使用 select 系统调用仅读取数据是否可用,然后从 goroutine 异步返回,但何必呢?如果你知道输入的结尾应该是什么,那么就返回。如果输入流结束,您将获得EOF。如果您不知道输入流的结尾是什么,那么您要么有错误,要么仍然需要保持 Read 处于打开状态。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2018-02-24
  • 1970-01-01
  • 2012-06-19
相关资源
最近更新 更多