Threads and Pipes in Console Apps

原文链接: Threads and Pipes in Console Apps

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

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

int  _tmain( int  argc, _TCHAR *  argv[])
{
 
for(int i = 0; i < 100; i++)
     
/* test */
      
int r = 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 */
      
int w = rand() % 500;
      Sleep(
200 + w);
     }
 /* test */
     
return 0;
}
    
        程序创建了两个线程:一个线程处理stdout,另一个线程处理stdin,主线程从两个工作线程收集数据并显示数据,当两个工作线程都结束时,主线程就结束。现在的问题是用什么机制来实现。作者使用了进程间队列机制,I/O完成端口,
class  CommandLine 
{
    
public:
       CommandLine() 
{ HTML = FALSE; IsUnicode = FALSE; program = NULL; }
       BOOL HTML;
//是否使用html
       BOOL IsUnicode;//是否使用unicode
       LPTSTR program;//目标程序名称
}
//  class CommandLine

父进程程序:

  int  _tmain( int  argc, _TCHAR *  argv[])
   
{
    CommandLine cmd;
    
for(int i = 1; i < argc; i++)
       
/* scan args */
        CString arg 
= 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(
"Unrecognized option \"%s\"\n"), arg);
            
return Result::INVALID_ARGUMENT;
           }
 /* option */
        
if(cmd.program != NULL)
           
/* two files */
            _ftprintf(stderr, _T(
"Two command directives given:\n  [1] \"%s\"\n  [2]\"%s\"\n"),
                      cmd,
                      arg);
            
return Result::TWO_COMMANDS;        
           }
 /* two files */
        cmd.program 
= argv[i];//目标程序名称
       }
 /* scan args */
    
if(cmd.program == NULL)
       
/* no args */
        _ftprintf(stderr, _T(
"need program to run\n"));
        
return Result::NO_PROGRAM;
       }
 /* no args */
    SmartHandle iocp 
= ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 00);//创建IO完成端口

    
if(iocp == NULL)
       
/* failed iocp */
        DWORD err 
= ::GetLastError();
        _ftprintf(stderr, _T(
"CreateIoCompletionPort failed, error %s\n"), ErrorString(err));
        
return Result::IOCP_FAILED;
       }
 /* failed iocp */
    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))
       
/* failed stdout */
        DWORD err 
= ::GetLastError();
        _tprintf(_T(
"stdout pipe failure: %s\n"), ErrorString(err));
        
return Result::STDOUT_CREATION_FAILED;
       }
 /* failed stdout */
    
//创建管道
    if(!::CreatePipe((LPHANDLE)stderr_read, (LPHANDLE)stderr_write, &sa, PIPE_BUFFER_SIZE))
       
/* failed stderr */
        DWORD err 
= ::GetLastError();
        _tprintf(_T(
"stderr pipe failure: %s\n"), ErrorString(err));
        
return Result::STDERR_CREATION_FAILED;
       }
 /* failed stderr */
    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))
       
/* failed */
        DWORD err 
= ::GetLastError();
        _tprintf(_T(
"CreateProcess failed for \"%s\": %s"), cmd.program, ErrorString(err));
        
return Result::CREATEPROCESS_FAILED;
       }
 /* 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)
       
/* thread create failed */
        DWORD err 
= ::GetLastError();
        _ftprintf(stderr, _T(
"Thread creation for stdout failed, error %s\n"), ErrorString(err));
        
return Result::THREAD_FAILURE;
       }
 /* thread create failed */        

    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)
       
/* thread create failed */
        DWORD err 
= ::GetLastError();
        _ftprintf(stderr, _T(
"Thread creation for stderr failed, error %s\n"), ErrorString(err));
        
return Result::THREAD_FAILURE;
       }
 /* 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))
       
/* watch pipes */
       
//异步方式监控管道
        OVERLAPPED * ovl;
        DWORD bytesRead;
        ULONG_PTR key;
        
//获取完成端口队列状态,获取从两个子线程到来的数据,key表明来自哪个线程已经完成了I/O操作,bytesRead表明数据来源
        BOOL ok = ::GetQueuedCompletionStatus(iocp, &bytesRead, &key, &ovl, INFINITE);
        
if(!ok)
           
/* failed */
            DWORD err 
= ::GetLastError();
            result 
= Result::IOCP_ERROR;
            _ftprintf(stderr, _T(
"GetQueuedCompletionStatus failed, error %s\n"), ErrorString(err));
            
break;
           }
 /* 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;
       }
 /* watch pipes */
    stdout_read.Close();
    stderr_read.Close();
    
return result;
   }
   //  _tmain

智能句柄类

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

 

class  SmartHandle 
{//智能句柄类
    public:
       SmartHandle() 
{ handle = NULL; }
       SmartHandle(HANDLE h) 
{ handle = h; }
       
virtual ~SmartHandle() { Close();}
    
public:
       
operator HANDLE() return handle; }
       
operator LPHANDLE() return & handle; }
       
bool operator==(HANDLE h) return handle == h; }
       SmartHandle 
& operator=(HANDLE h) { handle = h; return *this; }
    
public:
       
void Close() if(handle != NULL) ::CloseHandle(handle); handle = NULL; }
    
protected:
       HANDLE handle;
}
;

线程参数类:

class  ThreadParms
{//线程参数
    public:
       ThreadParms(HANDLE h, SourceFlags::FlagType f, HANDLE io, BOOL uni) 
       
{
          stream 
= h;
          flags 
= f;
          iocp 
= io;
          IsUnicode 
= uni;
       }

    
public:
       HANDLE stream;
//管道读端口
       SourceFlags::FlagType flags;//指明数据源
       HANDLE iocp;//I/O完成端口
       BOOL IsUnicode;//是否使用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 __stdcall reader(LPVOID p)
    
{//读线程工作函数
     ThreadParms * parms = (ThreadParms *)p;
     PipeReader pipe(parms
->stream, parms->IsUnicode);//创建管道读者
     CString Prefix;
     
while(TRUE)
        
/* processing loop */
         
//读管道数据
         if(!pipe.Read())
            
/* failed stream */
             
break;
            }
 /* failed stream */
         FormatAndOutput(pipe.GetString(), Prefix, parms);
        }
 /* processing loop */
     
if(!Prefix.IsEmpty())
        
/* write out last line */
         CString text(_T(
"\r\n"));
         FormatAndOutput(text, Prefix, parms);
        }
 /* 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 
{//管道读者
    protected:
       
static const UINT MAX_BUFFER = 1024;//缓存大小
    public:
       PipeReader(HANDLE str, BOOL uni) 
{ Init(); stream = str; IsUnicode = uni; }
       CString GetString() 
       

           
if(IsUnicode) 
               
return CString((LPCWSTR)buffer); 
           
else 
               
return CString((LPCSTR)buffer); 
       }

       BOOL Read() 
       
{//从管道中读取数据
          if(Offset == 1)
             buffer[
0= reread;
          
if(!ReadFile(stream, &buffer[Offset], MAX_BUFFER - (IsUnicode ? sizeof(WCHAR) : sizeof(char)), &bytesRead, NULL))
             
return FALSE;
          
if(IsUnicode)
             
/* unicode pipe */
              
if((Offset + bytesRead) & 1)
                 
/* 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
                 }
 /* odd bytes read */
              
else
                 
/* even bytes read */
                  Offset 
= 0// offset for next read
                 }
 /* even bytes read */
              buffer[Offset 
+ bytesRead] = 0;
              buffer[Offset 
+ bytesRead + 1= 0// create Unicode NUL
             }
 /* unicode pipe */
          
else
             
/* ANSI pipe */
              buffer[bytesRead] 
= '\0';
             }
 /* ANSI pipe */
         
return TRUE;
       }
 // PipeReader::Read
    protected:
       
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

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

你可能感兴趣的:(console)