原文链接:
Threads and Pipes in Console Apps
控制台程序中的线程和管道
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
}
;
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
控制台程序中的线程和管道
问题是:如何创建一个可能阻塞的程序,但在阻塞的时候能当数据可读的时候从stdour和stderr中接收数据。本文的目的是展示如何在控制台程序中使用多线程。
子进程程序:














































父进程程序:





















































































































































































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



































线程参数类:





















|
|
| Meaning |
| 0 |
|
|
| 0 |
|
|
0 |
|
|
|
0 |
|
|
|
读线程工作函数














































































































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