io.Pipe和尝试写入/读取时的死锁问题

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

io.Pipe and deadlock when trying to write/read

问题

我试图理解这段代码的基本逻辑已经几个小时了,但没有进展。下面的代码在第一次迭代后返回死锁。如果在io.Copy之前关闭写入器(writer),那么死锁就会消失,但是什么都不会打印出来(因为在读取之前关闭了管道的写入端)。

func main() {
    reader, writer := io.Pipe()
    c := make(chan string)

    go func() {
        for i := 0; i < 5; i++ {
            text := fmt.Sprintf("hello %vth time", i+1)
            c <- text
        }

        close(c)
    }()

    for msg := range c {
        msg = fmt.Sprintf("\nreceived from channel -> %v\n", msg)

        go fmt.Fprint(writer, msg)
        io.Copy(os.Stdout, reader)
        writer.Close()
    }
}

运行代码后出现以下错误:

> received from channel -> hello 1th time fatal error: all goroutines
> are asleep - deadlock!
> 
> goroutine 1 [select]: io.(*pipe).read(0xc000130120, {0xc00013e000,
> 0x8000, 0xc00011e001?})
>         /usr/lib/go/src/io/pipe.go:57 +0xb1 io.(*PipeReader).Read(0x0?, {0xc00013e000?, 0xc00011e050?, 0x10?})
>         /usr/lib/go/src/io/pipe.go:136 +0x25 io.copyBuffer({0x4bde98, 0xc00011e050}, {0x4bddb8, 0xc00012e018}, {0x0, 0x0, 0x0})
>         /usr/lib/go/src/io/io.go:427 +0x1b2 io.Copy(...)
>         /usr/lib/go/src/io/io.go:386 os.genericReadFrom(0x101c00002c500?, {0x4bddb8, 0xc00012e018})
>         /usr/lib/go/src/os/file.go:161 +0x67 os.(*File).ReadFrom(0xc00012e008, {0x4bddb8, 0xc00012e018})
>         /usr/lib/go/src/os/file.go:155 +0x1b0 io.copyBuffer({0x4bde38, 0xc00012e008}, {0x4bddb8, 0xc00012e018}, {0x0, 0x0, 0x0})
>         /usr/lib/go/src/io/io.go:413 +0x14b io.Copy(...)
>         /usr/lib/go/src/io/io.go:386 main.pipetest()
>         /home/stranger/source-code/golang/ipctest/pipes/main.go:39 +0x1ae main.main()
>         /home/stranger/source-code/golang/ipctest/pipes/main.go:10 +0x17
> 
> goroutine 18 [chan send]: main.pipetest.func1()
>         /home/stranger/source-code/golang/ipctest/pipes/main.go:29 +0x85 created by main.pipetest
>         /home/stranger/source-code/golang/ipctest/pipes/main.go:26 +0x17a exit status 2
英文:

I tried to understand underlying logic for several hours but no progress. This code below returns deadlock after 1st iteration. If I close writer before io.Copy than deadlock disappears but nothing is printed(since pipe write end is closed before read)

func main() {
    reader, writer := io.Pipe()
	c := make(chan string)

	go func() {
		for i := 0; i &lt; 5; i++ {
			text := fmt.Sprintf(&quot;hello %vth time&quot;, i+1)
			c &lt;- text
		}

		close(c)
	}()

	for msg := range c {
		msg = fmt.Sprintf(&quot;\nreceived from channel -&gt; %v\n&quot;, msg)

		go fmt.Fprint(writer, msg)
		io.Copy(os.Stdout, reader)
		writer.Close()
	}

}

and this is the error after running the code

> received from channel -> hello 1th time fatal error: all goroutines
> are asleep - deadlock!
>
> goroutine 1 [select]: io.(*pipe).read(0xc000130120, {0xc00013e000,
> 0x8000, 0xc00011e001?})
> /usr/lib/go/src/io/pipe.go:57 +0xb1 io.(*PipeReader).Read(0x0?, {0xc00013e000?, 0xc00011e050?, 0x10?})
> /usr/lib/go/src/io/pipe.go:136 +0x25 io.copyBuffer({0x4bde98, 0xc00011e050}, {0x4bddb8, 0xc00012e018}, {0x0, 0x0, 0x0})
> /usr/lib/go/src/io/io.go:427 +0x1b2 io.Copy(...)
> /usr/lib/go/src/io/io.go:386 os.genericReadFrom(0x101c00002c500?, {0x4bddb8, 0xc00012e018})
> /usr/lib/go/src/os/file.go:161 +0x67 os.(*File).ReadFrom(0xc00012e008, {0x4bddb8, 0xc00012e018})
> /usr/lib/go/src/os/file.go:155 +0x1b0 io.copyBuffer({0x4bde38, 0xc00012e008}, {0x4bddb8, 0xc00012e018}, {0x0, 0x0, 0x0})
> /usr/lib/go/src/io/io.go:413 +0x14b io.Copy(...)
> /usr/lib/go/src/io/io.go:386 main.pipetest()
> /home/stranger/source-code/golang/ipctest/pipes/main.go:39 +0x1ae main.main()
> /home/stranger/source-code/golang/ipctest/pipes/main.go:10 +0x17
>
> goroutine 18 [chan send]: main.pipetest.func1()
> /home/stranger/source-code/golang/ipctest/pipes/main.go:29 +0x85 created by main.pipetest
> /home/stranger/source-code/golang/ipctest/pipes/main.go:26 +0x17a exit status 2

答案1

得分: 1

io.Copy会一直尝试复制,直到读取器达到EOF(在这种情况下,当管道关闭时)。由于您在io.Copy结束之后调用writer.Close()io.Copy将永远不会看到EOF,并且会一直挂起。

您的代码的另一个问题是您尝试多次关闭管道(每次循环代码重复时)。通常,可关闭的对象应该只关闭一次,并且在关闭后被认为不可用。如果您需要重新使用它们,应该创建一个新实例。

以下是您代码的修订版本:

func main() {
	c := make(chan string)

	go func() {
		for i := 0; i < 5; i++ {
			text := fmt.Sprintf("hello %vth time", i+1)
			c <- text
		}

		close(c)
	}()

	for msg := range c {
		msg = fmt.Sprintf("\nreceived from channel -> %v\n", msg)

		// 为此消息创建一个新的管道。
		reader, writer := io.Pipe()
		go func() {
			fmt.Fprint(writer, msg)
			// 写入消息后关闭管道。
			writer.Close()
		}()

		io.Copy(os.Stdout, reader)
	}
}
英文:

io.Copy keeps trying to copy until reader reaches EOF (in this case, when the pipe is closed). Since you call writer.Close() after io.Copy ends, io.Copy will never see that EOF, and hangs forever.

The other problem with your code is that you're trying to close the pipe multiple times (each time the loop code repeats). In general Closeable objects should only be closed once, and are assumed to be un-usable after being Closed. If you need to re-use them, you should create a new instance.

Here's a working revision of your code:

func main() {
	c := make(chan string)

	go func() {
		for i := 0; i &lt; 5; i++ {
			text := fmt.Sprintf(&quot;hello %vth time&quot;, i+1)
			c &lt;- text
		}

		close(c)
	}()

	for msg := range c {
		msg = fmt.Sprintf(&quot;\nreceived from channel -&gt; %v\n&quot;, msg)

		// Create a new pipe for this message.
		reader, writer := io.Pipe()
		go func() {
			fmt.Fprint(writer, msg)
			// Close the pipe after writing the message.
			writer.Close()
		}()

		io.Copy(os.Stdout, reader)
	}
}

huangapple
  • 本文由 发表于 2023年3月4日 14:00:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75633951.html
匿名

发表评论

匿名网友

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

确定