原文链接:Threads and Pipes in Console Apps
控制台程序中的线程和管道
问题是:如何创建一个可能阻塞的程序,但在阻塞的时候能当数据可读的时候从stdour和stderr中接收数据。本文的目的是展示如何在控制台程序中使用多线程。
子进程程序:
int _tmain(int argc, _TCHAR* argv[])
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
for(int i = 0; i < 100; i++)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* test */
int r = rand();
if(r & 0x100)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* stderr */
_ftprintf(stderr, _T("%4d: error\n"), i);
fflush(stderr);
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* stderr */
else
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* stdout */
_ftprintf(stdout, _T("%4d: output\n"), i);
fflush(stdout);
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* stdout */
int w = rand() % 500;
Sleep(200 + w);
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* test */
return 0;
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
程序创建了两个线程:一个线程处理stdout,另一个线程处理stdin,主线程从两个工作线程收集数据并显示数据,当两个工作线程都结束时,主线程就结束。现在的问题是用什么机制来实现。作者使用了进程间队列机制,I/O完成端口,
class CommandLine
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{
public:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
CommandLine()
{ HTML = FALSE; IsUnicode = FALSE; program = NULL; }
BOOL HTML;//是否使用html
BOOL IsUnicode;//是否使用unicode
LPTSTR program;//目标程序名称
}; // class CommandLine
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
父进程程序:
int _tmain(int argc, _TCHAR* argv[])
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
CommandLine cmd;
for(int i = 1; i < argc; i++)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* scan args */
CString arg = argv[i];
if(arg[0] == _T('-'))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* option */
if(arg == _T("-u"))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* unicode */
cmd.IsUnicode = TRUE;//使用unicode
continue;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* unicode */
if(arg == _T("-html"))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* html */
cmd.HTML = TRUE;//使用html
continue;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* html */
_ftprintf(stderr, _T("Unrecognized option \"%s\"\n"), arg);
return Result::INVALID_ARGUMENT;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* option */
if(cmd.program != NULL)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* two files */
_ftprintf(stderr, _T("Two command directives given:\n [1] \"%s\"\n [2]\"%s\"\n"),
cmd,
arg);
return Result::TWO_COMMANDS;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* two files */
cmd.program = argv[i];//目标程序名称
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* scan args */
if(cmd.program == NULL)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* no args */
_ftprintf(stderr, _T("need program to run\n"));
return Result::NO_PROGRAM;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* no args */
SmartHandle iocp = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建IO完成端口
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
if(iocp == NULL)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed iocp */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("CreateIoCompletionPort failed, error %s\n"), ErrorString(err));
return Result::IOCP_FAILED;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed iocp */
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
SECURITY_ATTRIBUTES sa =
{sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
SmartHandle stdout_read;
SmartHandle stdout_write;
SmartHandle stderr_read;
SmartHandle stderr_write;
static const UINT PIPE_BUFFER_SIZE = 32;//管道缓冲区大小
//创建管道
if(!::CreatePipe((LPHANDLE)stdout_read, (LPHANDLE)stdout_write, &sa, PIPE_BUFFER_SIZE))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed stdout */
DWORD err = ::GetLastError();
_tprintf(_T("stdout pipe failure: %s\n"), ErrorString(err));
return Result::STDOUT_CREATION_FAILED;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed stdout */
//创建管道
if(!::CreatePipe((LPHANDLE)stderr_read, (LPHANDLE)stderr_write, &sa, PIPE_BUFFER_SIZE))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed stderr */
DWORD err = ::GetLastError();
_tprintf(_T("stderr pipe failure: %s\n"), ErrorString(err));
return Result::STDERR_CREATION_FAILED;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed stderr */
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
STARTUPINFO startup =
{sizeof(STARTUPINFO)};
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup.wShowWindow = SW_HIDE;
//子进程使用的标准输入和标准输出是父进程的写管道
startup.hStdOutput = stdout_write;
startup.hStdError = stderr_write;
PROCESS_INFORMATION procinfo;
//创建子进程,重定向标准输出和标准错误
if(!::CreateProcess(NULL, cmd.program, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &startup, &procinfo))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed */
DWORD err = ::GetLastError();
_tprintf(_T("CreateProcess failed for \"%s\": %s"), cmd.program, ErrorString(err));
return Result::CREATEPROCESS_FAILED;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed */
::CloseHandle(procinfo.hProcess); // handle will never be needed
::CloseHandle(procinfo.hThread); // handle will never be needed
//在父进程这端关闭子进程要使用的管道端口
stdout_write.Close(); // Close our end of the pipe
stderr_write.Close(); // Close our end of the pipe
unsigned id;
//创建处理stdout的子线程
SmartHandle stdoutThread = (HANDLE)_beginthreadex(NULL, 0, reader, new ThreadParms(stdout_read, SourceFlags::StdOut, iocp, cmd.IsUnicode), 0, &id);
if(stdoutThread == NULL)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* thread create failed */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("Thread creation for stdout failed, error %s\n"), ErrorString(err));
return Result::THREAD_FAILURE;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* thread create failed */
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
stdoutThread.Close(); // handle will never be used
//创建处理stderr的子线程
SmartHandle stderrThread = (HANDLE)_beginthreadex(NULL, 0, reader, new ThreadParms(stderr_read, SourceFlags::StdErr, iocp, cmd.IsUnicode), 0, &id);
if(stderrThread == NULL)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* thread create failed */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("Thread creation for stderr failed, error %s\n"), ErrorString(err));
return Result::THREAD_FAILURE;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* thread create failed */
stderrThread.Close(); // handle will never be used
SourceFlags::FlagType broken = SourceFlags::None;
Result::Type result = Result::SUCCESS;
while(broken != (SourceFlags::StdOut | SourceFlags::StdErr))
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* watch pipes */
//异步方式监控管道
OVERLAPPED * ovl;
DWORD bytesRead;
ULONG_PTR key;
//获取完成端口队列状态,获取从两个子线程到来的数据,key表明来自哪个线程已经完成了I/O操作,bytesRead表明数据来源
BOOL ok = ::GetQueuedCompletionStatus(iocp, &bytesRead, &key, &ovl, INFINITE);
if(!ok)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed */
DWORD err = ::GetLastError();
result = Result::IOCP_ERROR;
_ftprintf(stderr, _T("GetQueuedCompletionStatus failed, error %s\n"), ErrorString(err));
break;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed */
broken = (SourceFlags::FlagType)(broken | (int)key);
//终止信号到来
if(key != 0)
continue; // termination notifications contain no data
//输出数据
CString * s = (CString *)ovl;
WriteToOutput(*s, (SourceFlags::FlagType)bytesRead, cmd);
delete s;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* watch pipes */
stdout_read.Close();
stderr_read.Close();
return result;
} // _tmain
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
智能句柄类
用来处理句柄的生存期问题:
class SmartHandle
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{//智能句柄类
public:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
SmartHandle()
{ handle = NULL; }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
SmartHandle(HANDLE h)
{ handle = h; }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
virtual ~SmartHandle()
{ Close();}
public:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
operator HANDLE()
{ return handle; }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
operator LPHANDLE()
{ return & handle; }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
bool operator==(HANDLE h)
{ return handle == h; }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
SmartHandle & operator=(HANDLE h)
{ handle = h; return *this; }
public:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
void Close()
{ if(handle != NULL) ::CloseHandle(handle); handle = NULL; }
protected:
HANDLE handle;
};
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
线程参数类:
class ThreadParms
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{//线程参数
public:
ThreadParms(HANDLE h, SourceFlags::FlagType f, HANDLE io, BOOL uni)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
stream = h;
flags = f;
iocp = io;
IsUnicode = uni;
}
public:
HANDLE stream;//管道读端口
SourceFlags::FlagType flags;//指明数据源
HANDLE iocp;//I/O完成端口
BOOL IsUnicode;//是否使用unicode
};
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
DWORD NumberOfBytesTransferred
|
ULONG_PTR CompletionKey
|
LPOVERLAPPED Overlapped
|
Meaning
|
SourceFlags::StdOut
|
0
|
(LPOVERLAPPED)(CString *)
|
stdout line to display
|
SourceFlags::StdErr
|
0
|
(LPOVERALLPED)(CString *)
|
stderr line to display
|
0
|
SourceFlags::StdOut
|
NULL
|
stdout has terminated
|
0
|
SourceFlags::StdErr
|
NULL
|
stderr has terminated
|
读线程工作函数
UINT __stdcall reader(LPVOID p)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{//读线程工作函数
ThreadParms * parms = (ThreadParms *)p;
PipeReader pipe(parms->stream, parms->IsUnicode);//创建管道读者
CString Prefix;
while(TRUE)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* processing loop */
//读管道数据
if(!pipe.Read())
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* failed stream */
break;
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* failed stream */
FormatAndOutput(pipe.GetString(), Prefix, parms);
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* processing loop */
if(!Prefix.IsEmpty())
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* write out last line */
CString text(_T("\r\n"));
FormatAndOutput(text, Prefix, parms);
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* write out last line */
//posts an I/O completion packet to an I/O completion port
//发出完成信息,没有数据,因此第二个参数为,第三个参数表明完成的是哪个
::PostQueuedCompletionStatus(parms->iocp, 0, parms->flags, NULL);
delete parms;
return 0;
} // reader
class PipeReader
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
{//管道读者
protected:
static const UINT MAX_BUFFER = 1024;//缓存大小
public:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
PipeReader(HANDLE str, BOOL uni)
{ Init(); stream = str; IsUnicode = uni; }
CString GetString()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if(IsUnicode)
return CString((LPCWSTR)buffer);
else
return CString((LPCSTR)buffer);
}
BOOL Read()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{//从管道中读取数据
if(Offset == 1)
buffer[0] = reread;
if(!ReadFile(stream, &buffer[Offset], MAX_BUFFER - (IsUnicode ? sizeof(WCHAR) : sizeof(char)), &bytesRead, NULL))
return FALSE;
if(IsUnicode)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* unicode pipe */
if((Offset + bytesRead) & 1)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* odd bytes read */
Offset = 1; // offset for next read
reread = buffer[Offset + bytesRead - 1]; // force reread
buffer[Offset + bytesRead - 1] = 0; // remove from current buffer
bytesRead--; // pretend we didn't see it
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* odd bytes read */
else
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* even bytes read */
Offset = 0; // offset for next read
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* even bytes read */
buffer[Offset + bytesRead] = 0;
buffer[Offset + bytesRead + 1] = 0; // create Unicode NUL
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* unicode pipe */
else
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{ /**//* ANSI pipe */
buffer[bytesRead] = '\0';
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
} /**//* ANSI pipe */
return TRUE;
} // PipeReader::Read
protected:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
void Init()
{ stream = NULL; Offset = 0; IsUnicode = FALSE; }
BOOL IsUnicode;//是否使用unicode
HANDLE stream;//管道读端口
protected:
BYTE buffer[MAX_BUFFER];//缓冲区
DWORD Offset;
BYTE reread;
DWORD bytesRead;
}; // class PipeReader
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
小结
子进程往管道的两个写端口写数据,父进程从管道的两个读端口读数据,为了读数据,父进程创建了两个子线程,一个子线程从stdout中读数据,一个子线程从stderr中读数据,为了能异步读取数据,使用了I/O完成端口。