Go语言io.Pipe读写操作出现死锁问题求助
问题原因
这段代码在第一次循环后触发死锁,核心问题出在两个地方:
io.Copy的阻塞逻辑:io.Copy(os.Stdout, reader)会持续从PipeReader读取数据,直到对应的PipeWriter被关闭(触发EOF)。但原代码中writer.Close()放在io.Copy之后,第一次循环时,goroutine写完第一个消息后writer并未关闭,io.Copy读完消息后会一直阻塞等待更多数据,导致主goroutine无法进入下一次循环接收channel消息。- 无缓冲channel的发送阻塞:
c是无缓冲channel,发送goroutine在发送第二个消息时,会等待主goroutine接收,但主goroutine卡在io.Copy中,发送goroutine因此阻塞。最终所有goroutine都进入休眠状态,触发死锁。
原代码
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 +0x17goroutine 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
修复方案
调整io.Copy的执行时机,让它在后台持续读取管道数据,同时确保所有写入完成后关闭管道,避免阻塞:
import ( "fmt" "io" "os" "sync" ) func main() { reader, writer := io.Pipe() c := make(chan string) // 启动goroutine发送消息到channel go func() { for i := 0; i < 5; i++ { text := fmt.Sprintf("hello %vth time", i+1) c <- text } close(c) }() // 启动单独goroutine处理管道输出,用WaitGroup等待完成 var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() io.Copy(os.Stdout, reader) }() // 遍历channel,将消息写入管道 for msg := range c { msg = fmt.Sprintf("\nreceived from channel -> %v\n", msg) fmt.Fprint(writer, msg) } // 所有消息写入完成后关闭writer,触发reader的EOF writer.Close() // 等待io.Copy完成所有数据输出 wg.Wait() }
关键说明
- 将
io.Copy放到后台goroutine,主goroutine可以继续接收channel消息,避免阻塞。 - 遍历完channel后再关闭
writer,确保所有消息都写入管道,io.Copy会读取完所有数据后因EOF自动退出。 - 使用
sync.WaitGroup等待io.Copy完成,保证所有数据输出到stdout后程序才退出。
内容的提问来源于stack exchange,提问作者VPavliashvili

