关闭管道 fd 时发生死锁
我正在将一些 Unix 代码移植到 Windows,它将 stderr 和 stdout 重定向到我创建的管道,并且有一个线程从该管道读取数据,然后将输出发送到调试控制台。这在 Unix 上工作得很好,但我无法在 Windows 上工作。当管道的读取端关闭时,就会出现问题。它不是将 EOF 写入管道(这会导致线程退出),而是死锁。为什么?
一种解决方法是跳过关闭调用,这让我有点担心,但由于我的过程是短暂的,也许这没什么大不了的?
这是说明问题的示例代码...我正在使用 VS 2010:
#include <cstdio>
#include <tchar.h>
#include <iostream>
#include <vector>
#include <fcntl.h>
#include <Windows.h>
#include <io.h>
#define posix_open _open
#define posix_read _read
#define posix_write _write
#define posix_pipe( fds ) _pipe( fds, 8096, _O_BINARY)
#define posix_close _close
#define posix_dup _dup
#define posix_dup2 _dup2
#define posix_fileno _fileno
using namespace std;
static const int PIPE_READ = 0;
static const int PIPE_WRITE = 1;
DWORD __stdcall PipeReaderFunc(void* readFd)
{
int pipeFd = *((int*)readFd);
vector< char > buffer(8096);
while( posix_read(pipeFd, &buffer[0], buffer.size() ) != 0 )
{
OutputDebugString( &buffer[0] );
}
return 0;
}
void test()
{
int pipefd[2] = {-1,-1};
if( posix_pipe( pipefd ) < 0 )
{ throw std::exception( "Failed to initialize pipe." );}
int stdoutOrig = posix_dup( _fileno(stdout) );
int stderrOrig = posix_dup( _fileno(stderr) );
if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stdout) ) ) // closes stdout
{throw exception( "Failed to dup stdout fd." );}
if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stderr) ) ) // closes stderr
{throw exception( "Failed to dup stderr fd." );}
HANDLE hThread = CreateThread( NULL, 0, PipeReaderFunc, &pipefd[PIPE_READ], 0, NULL);
if( NULL == hThread )
{throw exception("Failed to create thread");}
cout << "This should go to the debug console" << endl;
Sleep(1000); // Give time for the thread to read from the pipe
posix_close( stdoutOrig );
posix_close( stderrOrig );
posix_close( pipefd[PIPE_WRITE] );
// Deadlock occurs on this line
posix_close( pipefd[PIPE_READ] );
// This is commented out because it has no effect right now.
//WaitForSingleObject( hThread, INFINITE );
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{ test(); }
catch( exception& ex )
{ cerr << ex.what() << endl; }
return 0;
}
感谢您提供有关如何解决此问题的任何想法!
I am porting some Unix code to Windows which redirects stderr and stdout to a pipe that I created and there's a thread which reads from this pipe and then sends the output to the debug console. This works fine on Unix, but I can't get it working on Windows. The problem occurs when the read side of the pipe is closed. Instead of writing EOF to the pipe which would cause the thread to quit, it deadlocks. Why?
One workaround is to skip the call to close, which worries me a little, but since my process is short lived, maybe it isn't a big deal?
Here is sample code which illustrates the problem... I'm using VS 2010:
#include <cstdio>
#include <tchar.h>
#include <iostream>
#include <vector>
#include <fcntl.h>
#include <Windows.h>
#include <io.h>
#define posix_open _open
#define posix_read _read
#define posix_write _write
#define posix_pipe( fds ) _pipe( fds, 8096, _O_BINARY)
#define posix_close _close
#define posix_dup _dup
#define posix_dup2 _dup2
#define posix_fileno _fileno
using namespace std;
static const int PIPE_READ = 0;
static const int PIPE_WRITE = 1;
DWORD __stdcall PipeReaderFunc(void* readFd)
{
int pipeFd = *((int*)readFd);
vector< char > buffer(8096);
while( posix_read(pipeFd, &buffer[0], buffer.size() ) != 0 )
{
OutputDebugString( &buffer[0] );
}
return 0;
}
void test()
{
int pipefd[2] = {-1,-1};
if( posix_pipe( pipefd ) < 0 )
{ throw std::exception( "Failed to initialize pipe." );}
int stdoutOrig = posix_dup( _fileno(stdout) );
int stderrOrig = posix_dup( _fileno(stderr) );
if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stdout) ) ) // closes stdout
{throw exception( "Failed to dup stdout fd." );}
if( -1 == posix_dup2( pipefd[PIPE_WRITE], posix_fileno(stderr) ) ) // closes stderr
{throw exception( "Failed to dup stderr fd." );}
HANDLE hThread = CreateThread( NULL, 0, PipeReaderFunc, &pipefd[PIPE_READ], 0, NULL);
if( NULL == hThread )
{throw exception("Failed to create thread");}
cout << "This should go to the debug console" << endl;
Sleep(1000); // Give time for the thread to read from the pipe
posix_close( stdoutOrig );
posix_close( stderrOrig );
posix_close( pipefd[PIPE_WRITE] );
// Deadlock occurs on this line
posix_close( pipefd[PIPE_READ] );
// This is commented out because it has no effect right now.
//WaitForSingleObject( hThread, INFINITE );
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{ test(); }
catch( exception& ex )
{ cerr << ex.what() << endl; }
return 0;
}
Thanks for any ideas on how to resolve this!!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当
_close
在被阻塞时调用时,Windows 上的_read
实现可能会返回 -1。如果在_close
完成后调用它,则根据文档,它应该返回 -1。因此,看起来您的线程会卡在主循环中,因为它仅在返回值为零时终止。也许您应该将循环条件从!= 0
更改为> 0 并尝试一下。
编辑:
我正在查看文档的错误部分。根据文档,当指向它的所有描述符都关闭时,管道的句柄也将关闭。我认为您想实现这一点,但犯了一个小错误。如果将: 替换
为,
程序将正确执行并终止。这是因为有两个 fd 指向读取器句柄,而您只关闭了一个。于是线程就愉快地被阻塞了,等待更多的数据。当您还关闭 dup ed 句柄时,read 返回零(正如您所指出的),并且线程终止。
It is possible that the
_read
implementation on windows returns -1 when_close
is called while it is blocked. If it is called after your_close
completes, it should return -1 as per the documentation. So, it looks like your thread would get stuck in the main loop, because it only terminates when the return value is zero. Maybe you should change your loop condition from!= 0
to> 0
and try it.EDIT:
I was looking at the wrong part of the documentation. According to the documentation, the handle from pipe is closed when all descriptors pointing to it are closed. I think you wanted to implement that, but made a small error. If you replace:
with
the program executes and terminates correctly. This is because there are two fds pointing to the reader handle, and you were closing only one. So the thread was happily blocked, waiting for more data. When you also close the
dup
ed handle, theread
returns zero (as you've pointed out), and the thread terminates.如果您在 Unix 上使用
fork()
和exec*()
系列函数之一,而不是在 Windows 上使用CreateThread()
,则子进程将在执行 dup2() 操作之后和执行 exec 之前关闭管道的读写端。父进程将关闭它不打算使用的管道的一端。 (通常)这是必要的,以确保没有杂散的打开文件描述符引用管道。如果管道中有杂散的开放写入端,则管道上的读取器将永远不会收到 EOF。如果管道中有一个杂散的开放读端,则写入器可能会阻塞(死锁),等待读取器读取数据 - 即使该读取器与正在进行写入的进程是同一进程。以此类推,您应该确保线程仅需要打开管道的末端,并应关闭所有其他部分。
If you were using
fork()
and one of theexec*()
family of functions on Unix instead ofCreateThread()
on Windows, the child process would close the read and write ends of the pipe after doing thedup2()
operations and before doing the exec. The parent process would close whichever end of the pipe it was not going to use. This is necessary (in general) to ensure that there are no stray open file descriptors referring to the pipe. If there is a stray open write end to the pipe, the reader(s) on the pipe will never get EOF. If there is a stray open read end to the pipe, the writer may block (deadlock) waiting for a reader read the data - even if that reader is the same process that is doing the writing.By analogy, you should ensure that your thread has only the ends of the pipe that it needs open, and should close all the other parts.