Threads and Pipes in Console Apps

本文介绍了一个控制台程序如何通过使用多线程和管道来处理子进程的stdout和stderr输出。父进程创建两个线程分别读取stdout和stderr,并使用I/O完成端口机制异步地监控管道。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接: Threads and Pipes in Console Apps

控制台程序中的线程和管道

问题是:如何创建一个可能阻塞的程序,但在阻塞的时候能当数据可读的时候从stdourstderr中接收数据。本文的目的是展示如何在控制台程序中使用多线程。
子进程程序:

int _tmain( int argc,_TCHAR * argv[])
{
for(inti=0;i<100;i++)
{/**//*test*/
intr=rand();
if(r&0x100)
{/**//*stderr*/
_ftprintf(stderr,_T(
"%4d:error/n"),i);
fflush(stderr);
}
/**//*stderr*/
else
{/**//*stdout*/
_ftprintf(stdout,_T(
"%4d:output/n"),i);
fflush(stdout);
}
/**//*stdout*/
intw=rand()%500;
Sleep(
200+w);
}
/**//*test*/
return0;
}

程序创建了两个线程:一个线程处理 stdout ,另一个线程处理 stdin, 主线程从两个工作线程收集数据并显示数据,当两个工作线程都结束时,主线程就结束。现在的问题是用什么机制来实现。作者使用了进程间队列机制, I/O 完成端口,
class CommandLine
{
public:
CommandLine()
{HTML=FALSE;IsUnicode=FALSE;program=NULL;}
BOOLHTML;
//是否使用html
BOOLIsUnicode;//是否使用unicode
LPTSTRprogram;//目标程序名称
}
; // classCommandLine

父进程程序:

int _tmain( int argc,_TCHAR * argv[])
{
CommandLinecmd;
for(inti=1;i<argc;i++)
{/**//*scanargs*/
CStringarg
=argv[i];
if(arg[0]==_T('-'))
{/**//*option*/
if(arg==_T("-u"))
{/**//*unicode*/
cmd.IsUnicode
=TRUE;//使用unicode
continue;
}
/**//*unicode*/
if(arg==_T("-html"))
{/**//*html*/
cmd.HTML
=TRUE;//使用html
continue;
}
/**//*html*/
_ftprintf(stderr,_T(
"Unrecognizedoption/"%s/"/n"),arg);
returnResult::INVALID_ARGUMENT;
}
/**//*option*/
if(cmd.program!=NULL)
{/**//*twofiles*/
_ftprintf(stderr,_T(
"Twocommanddirectivesgiven:/n[1]/"%s/"/n[2]/"%s/"/n"),
cmd,
arg);
returnResult::TWO_COMMANDS;
}
/**//*twofiles*/
cmd.program
=argv[i];//目标程序名称
}
/**//*scanargs*/
if(cmd.program==NULL)
{/**//*noargs*/
_ftprintf(stderr,_T(
"needprogramtorun/n"));
returnResult::NO_PROGRAM;
}
/**//*noargs*/
SmartHandleiocp
=::CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);//创建IO完成端口

if(iocp==NULL)
{/**//*failediocp*/
DWORDerr
=::GetLastError();
_ftprintf(stderr,_T(
"CreateIoCompletionPortfailed,error%s/n"),ErrorString(err));
returnResult::IOCP_FAILED;
}
/**//*failediocp*/
SECURITY_ATTRIBUTESsa
={sizeof(SECURITY_ATTRIBUTES),NULL,TRUE};
SmartHandlestdout_read;
SmartHandlestdout_write;
SmartHandlestderr_read;
SmartHandlestderr_write;
staticconstUINTPIPE_BUFFER_SIZE=32;//管道缓冲区大小
//创建管道
if(!::CreatePipe((LPHANDLE)stdout_read,(LPHANDLE)stdout_write,&sa,PIPE_BUFFER_SIZE))
{/**//*failedstdout*/
DWORDerr
=::GetLastError();
_tprintf(_T(
"stdoutpipefailure:%s/n"),ErrorString(err));
returnResult::STDOUT_CREATION_FAILED;
}
/**//*failedstdout*/
//创建管道
if(!::CreatePipe((LPHANDLE)stderr_read,(LPHANDLE)stderr_write,&sa,PIPE_BUFFER_SIZE))
{/**//*failedstderr*/
DWORDerr
=::GetLastError();
_tprintf(_T(
"stderrpipefailure:%s/n"),ErrorString(err));
returnResult::STDERR_CREATION_FAILED;
}
/**//*failedstderr*/
STARTUPINFOstartup
={sizeof(STARTUPINFO)};
startup.dwFlags
=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
startup.wShowWindow
=SW_HIDE;
//子进程使用的标准输入和标准输出是父进程的写管道
startup.hStdOutput=stdout_write;
startup.hStdError
=stderr_write;
PROCESS_INFORMATIONprocinfo;
//创建子进程,重定向标准输出和标准错误
if(!::CreateProcess(NULL,cmd.program,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,NULL,NULL,&startup,&procinfo))
{/**//*failed*/
DWORDerr
=::GetLastError();
_tprintf(_T(
"CreateProcessfailedfor/"%s/":%s"),cmd.program,ErrorString(err));
returnResult::CREATEPROCESS_FAILED;
}
/**//*failed*/
::CloseHandle(procinfo.hProcess);
//handlewillneverbeneeded
::CloseHandle(procinfo.hThread);//handlewillneverbeneeded
//在父进程这端关闭子进程要使用的管道端口
stdout_write.Close();//Closeourendofthepipe
stderr_write.Close();//Closeourendofthepipe
unsignedid;
//创建处理stdout的子线程
SmartHandlestdoutThread=(HANDLE)_beginthreadex(NULL,0,reader,newThreadParms(stdout_read,SourceFlags::StdOut,iocp,cmd.IsUnicode),0,&id);
if(stdoutThread==NULL)
{/**//*threadcreatefailed*/
DWORDerr
=::GetLastError();
_ftprintf(stderr,_T(
"Threadcreationforstdoutfailed,error%s/n"),ErrorString(err));
returnResult::THREAD_FAILURE;
}
/**//*threadcreatefailed*/

stdoutThread.Close();
//handlewillneverbeused
//创建处理stderr的子线程
SmartHandlestderrThread=(HANDLE)_beginthreadex(NULL,0,reader,newThreadParms(stderr_read,SourceFlags::StdErr,iocp,cmd.IsUnicode),0,&id);
if(stderrThread==NULL)
{/**//*threadcreatefailed*/
DWORDerr
=::GetLastError();
_ftprintf(stderr,_T(
"Threadcreationforstderrfailed,error%s/n"),ErrorString(err));
returnResult::THREAD_FAILURE;
}
/**//*threadcreatefailed*/
stderrThread.Close();
//handlewillneverbeused
SourceFlags::FlagTypebroken=SourceFlags::None;
Result::Typeresult
=Result::SUCCESS;
while(broken!=(SourceFlags::StdOut|SourceFlags::StdErr))
{/**//*watchpipes*/
//异步方式监控管道
OVERLAPPED*ovl;
DWORDbytesRead;
ULONG_PTRkey;
//获取完成端口队列状态,获取从两个子线程到来的数据,key表明来自哪个线程已经完成了I/O操作,bytesRead表明数据来源
BOOLok=::GetQueuedCompletionStatus(iocp,&bytesRead,&key,&ovl,INFINITE);
if(!ok)
{/**//*failed*/
DWORDerr
=::GetLastError();
result
=Result::IOCP_ERROR;
_ftprintf(stderr,_T(
"GetQueuedCompletionStatusfailed,error%s/n"),ErrorString(err));
break;
}
/**//*failed*/
broken
=(SourceFlags::FlagType)(broken|(int)key);
//终止信号到来
if(key!=0)
continue;//terminationnotificationscontainnodata
//输出数据
CString*s=(CString*)ovl;
WriteToOutput(
*s,(SourceFlags::FlagType)bytesRead,cmd);
deletes;
}
/**//*watchpipes*/
stdout_read.Close();
stderr_read.Close();
returnresult;
}
// _tmain

智能句柄类

用来处理句柄的生存期问题:

class SmartHandle
{//智能句柄类
public:
SmartHandle()
{handle=NULL;}
SmartHandle(HANDLEh)
{handle=h;}
virtual~SmartHandle(){Close();}
public:
operatorHANDLE(){returnhandle;}
operatorLPHANDLE(){return&handle;}
booloperator==(HANDLEh){returnhandle==h;}
SmartHandle
&operator=(HANDLEh){handle=h;return*this;}
public:
voidClose(){if(handle!=NULL)::CloseHandle(handle);handle=NULL;}
protected:
HANDLEhandle;
}
;

线程参数类:

class ThreadParms
{//线程参数
public:
ThreadParms(HANDLEh,SourceFlags::FlagTypef,HANDLEio,BOOLuni)
{
stream
=h;
flags
=f;
iocp
=io;
IsUnicode
=uni;
}

public:
HANDLEstream;
//管道读端口
SourceFlags::FlagTypeflags;//指明数据源
HANDLEiocp;//I/O完成端口
BOOLIsUnicode;//是否使用unicode
}
;

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__stdcallreader(LPVOIDp)
{//读线程工作函数
ThreadParms*parms=(ThreadParms*)p;
PipeReaderpipe(parms
->stream,parms->IsUnicode);//创建管道读者
CStringPrefix;
while(TRUE)
{/**//*processingloop*/
//读管道数据
if(!pipe.Read())
{/**//*failedstream*/
break;
}
/**//*failedstream*/
FormatAndOutput(pipe.GetString(),Prefix,parms);
}
/**//*processingloop*/
if(!Prefix.IsEmpty())
{/**//*writeoutlastline*/
CStringtext(_T(
"/r/n"));
FormatAndOutput(text,Prefix,parms);
}
/**//*writeoutlastline*/
//postsanI/OcompletionpackettoanI/Ocompletionport
//发出完成信息,没有数据,因此第二个参数为,第三个参数表明完成的是哪个
::PostQueuedCompletionStatus(parms->iocp,0,parms->flags,NULL);
deleteparms;
return0;
}
// reader
class PipeReader
{//管道读者
protected:
staticconstUINTMAX_BUFFER=1024;//缓存大小
public:
PipeReader(HANDLEstr,BOOLuni)
{Init();stream=str;IsUnicode=uni;}
CStringGetString()
{
if(IsUnicode)
returnCString((LPCWSTR)buffer);
else
returnCString((LPCSTR)buffer);
}

BOOLRead()
{//从管道中读取数据
if(Offset==1)
buffer[
0]=reread;
if(!ReadFile(stream,&buffer[Offset],MAX_BUFFER-(IsUnicode?sizeof(WCHAR):sizeof(char)),&bytesRead,NULL))
returnFALSE;
if(IsUnicode)
{/**//*unicodepipe*/
if((Offset+bytesRead)&1)
{/**//*oddbytesread*/
Offset
=1;//offsetfornextread
reread=buffer[Offset+bytesRead-1];//forcereread
buffer[Offset+bytesRead-1]=0;//removefromcurrentbuffer
bytesRead--;//pretendwedidn'tseeit
}
/**//*oddbytesread*/
else
{/**//*evenbytesread*/
Offset
=0;//offsetfornextread
}
/**//*evenbytesread*/
buffer[Offset
+bytesRead]=0;
buffer[Offset
+bytesRead+1]=0;//createUnicodeNUL
}
/**//*unicodepipe*/
else
{/**//*ANSIpipe*/
buffer[bytesRead]
='/0';
}
/**//*ANSIpipe*/
returnTRUE;
}
//PipeReader::Read
protected:
voidInit(){stream=NULL;Offset=0;IsUnicode=FALSE;}
BOOLIsUnicode;
//是否使用unicode
HANDLEstream;//管道读端口
protected:
BYTEbuffer[MAX_BUFFER];
//缓冲区
DWORDOffset;
BYTEreread;
DWORDbytesRead;
}
; // classPipeReader

小结

子进程往管道的两个写端口写数据,父进程从管道的两个读端口读数据,为了读数据,父进程创建了两个子线程,一个子线程从stdout中读数据,一个子线程从stderr中读数据,为了能异步读取数据,使用了I/O完成端口。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值