多线程下面日志输出-线程安全-消息队列循环输出

/×××××××××××Thread.h××××××××××××××/


#ifndef __THREAD__
#define __THREAD__

#ifdef WIN32
#include
#include
#else
#include
#include
#endif
using namespace std;
class ThreadException
{
private:
 string msg;
public:
 ThreadException(string m);
 ThreadException(const char* m);
 virtual ~ThreadException() {};
 virtual string getMessage() const;
}; 

class Thread
{
protected:
  string m_strName;

#ifdef WIN32 
  HANDLE m_hThread;
  CRITICAL_SECTION m_hMutex;
  LONG volatile itsWorkingThreadID;
  LONG volatile itsRunningFlag;
  LONG volatile itsSuspendedFlag;
#else
  pthread_t m_hThread;
  pthread_mutex_t m_hMutex;
  pthread_mutex_t m_hSuspendMutex;
  pthread_cond_t m_SuspendCondition;
  unsigned long itsWorkingThreadID;
  bool itsRunningFlag;
  bool itsSuspendedFlag;
#endif

  static bool itsShutdownInProgress;

public:
  Thread(const char* nm);
  virtual ~Thread();
  const char* getName() const;
  void start();
  virtual void run();
  static void sleep(long ms);
  void suspend();
  void resume();
  void stop(bool cancel=true);

  void setPriority(int p);
  void setAffinity(unsigned cpu);

  bool wait(long ms=5000);
  void release();
  
  bool is(const char* theName);
  void running();
  bool isRunning();
  bool isSuspended();
  
  static void shutdownInProgress();
  static bool isShuttingDown() { return itsShutdownInProgress; };
  static unsigned long threadID();  

public:
  // Thread priorities
  static const int P_ABOVE_NORMAL;
  static const int P_BELOW_NORMAL;
  static const int P_HIGHEST;
  static const int P_IDLE;
  static const int P_LOWEST;
  static const int P_NORMAL;
  static const int P_CRITICAL;
    
};

extern "C"
{
#ifdef WIN32 
 unsigned int _ou_thread_proc(void* param);
#else
 void* _ou_thread_proc(void* param);
#endif
}

#endif
    

/×××××××××××Thread.h××××××××××××××/



/××××××××××××××Thread.cpp×××××××××××××××××/


#define SILENT
#include "Thread.h"

#ifdef WIN32
const int Thread::P_ABOVE_NORMAL = THREAD_PRIORITY_ABOVE_NORMAL;
const int Thread::P_BELOW_NORMAL = THREAD_PRIORITY_BELOW_NORMAL;
const int Thread::P_HIGHEST = THREAD_PRIORITY_HIGHEST;
const int Thread::P_IDLE = THREAD_PRIORITY_IDLE;
const int Thread::P_LOWEST = THREAD_PRIORITY_LOWEST;
const int Thread::P_NORMAL = THREAD_PRIORITY_NORMAL;
const int Thread::P_CRITICAL = THREAD_PRIORITY_TIME_CRITICAL;
#define THREAD_NULL NULL
#define ASSIGN_LONG(dest,val) InterlockedExchange(&dest,val)
#define ASSIGN_BOOL(dest,val) InterlockedExchange(&dest,(LONG)val)
#define ASSIGN_PTR(dest,val)  InterlockedExchangePointer(&dest,val)
#else
#include
const int Thread::P_ABOVE_NORMAL = 0;
const int Thread::P_BELOW_NORMAL = 1;
const int Thread::P_HIGHEST = 2;
const int Thread::P_IDLE = 3;
const int Thread::P_LOWEST = 4;
const int Thread::P_NORMAL = 5;
const int Thread::P_CRITICAL = 6;
#define THREAD_NULL 0
#define ASSIGN_LONG(dest,val) dest=val
#define ASSIGN_BOOL(dest,val) dest=val
#define ASSIGN_PTR(dest,val)  dest=val
#endif

#define SUSPENDWAITMS 10

bool Thread::itsShutdownInProgress=false;

void Thread::running()
{
 ASSIGN_BOOL(itsRunningFlag,true);
};

bool Thread::isRunning()
{
 return (itsRunningFlag!=0) ? true : false;
};

bool Thread::isSuspended()
{
 return (itsSuspendedFlag!=0) ? true : false;
};

void Thread::shutdownInProgress()
{
 itsShutdownInProgress=true;
}

unsigned long Thread::threadID()
{
#ifdef WIN32
 return GetCurrentThreadId();
#else
 return (unsigned long)pthread_self();
#endif
}

/** Thread(const char* nm)
  * overloaded constructor
  * creates a Thread object identified by "nm"
**/ 
Thread::Thread(const char* nm)
{
 
 m_hThread = THREAD_NULL;
 m_strName = nm;
 ASSIGN_BOOL(itsRunningFlag,false);
 ASSIGN_BOOL(itsSuspendedFlag,false);
 ASSIGN_LONG(itsWorkingThreadID,0); 

#ifdef WIN32 
 InitializeCriticalSection(&m_hMutex);
#else
 pthread_mutex_init(&m_hMutex,NULL);
#endif

}

Thread::~Thread()
{
 
 if(m_hThread != THREAD_NULL)
 {
  stop(true);
 }

#ifdef WIN32
 DeleteCriticalSection(&m_hMutex); 
#else
 pthread_mutex_destroy(&m_hMutex);
#endif

}


/** getName()
  * return the Thread object's name as a string
**/ 
const char* Thread::getName() const

 return m_strName.c_str();
}

bool Thread::is(const char* theName)
{
 return (m_strName==theName);
}

/** run()
  * called by the thread callback _ou_thread_proc()
  * to be overridden by child classes of Thread
**/
void Thread::run()
{
 // Base run
}

/** sleep(long ms)
  * holds back the thread's execution for
  * "ms" milliseconds
**/

void Thread::sleep(long ms)
{
#ifdef WIN32
 Sleep(ms);
#else
    struct timespec abs_ts;
    struct timespec rm_ts;
 rm_ts.tv_sec = ms/1000;
 rm_ts.tv_nsec = ms%1000 *1000000;

 do
 {
  abs_ts.tv_sec = rm_ts.tv_sec;
  abs_ts.tv_nsec = rm_ts.tv_nsec;
 } while(nanosleep(&abs_ts,&rm_ts) < 0);
 
#endif

}

/** start()
  * creates a low-level thread object and calls the
  * run() function
**/


void Thread::start()
{
 
#ifdef WIN32 
 DWORD tid = 0; 
 m_hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)_ou_thread_proc,(Thread*)this,0,&tid);
 if(m_hThread == NULL)
 {
  
  throw ThreadException(string("Failed to create thread ->")+m_strName);
 }
 else
 {
  setPriority(Thread::P_NORMAL);
 }
#else
 pthread_mutex_init(&m_hSuspendMutex,NULL);
 pthread_cond_init(&m_SuspendCondition,NULL);
 int iret = pthread_create( &m_hThread, NULL, _ou_thread_proc,this);
 if(iret!=0)
 {
  TRACE("Fail to create thread")
  throw ThreadException(string("Failed to create thread ->")+m_strName);
 }
#endif
 
}

/** stop()
  * stops the running thread and frees the thread handle
**/
void Thread::stop(bool cancel)
{
 

 if(itsRunningFlag)
 {
  ASSIGN_BOOL(itsRunningFlag,false);

#ifdef WIN32
  if(isSuspended()==true)
   resume();
   
  if(cancel==true)
   TerminateThread(m_hThread,NULL);
  else
   WaitForSingleObject(m_hThread,INFINITE);
 
  CloseHandle(m_hThread);  
#else
  TRACE("Joining thread")
  if(cancel==true)
   pthread_cancel(m_hThread);
  else
   pthread_join(m_hThread,NULL);

  TRACE("Thread cleanup")   
  pthread_mutex_destroy(&m_hSuspendMutex);
  pthread_cond_destroy(&m_SuspendCondition);
#endif

  m_hThread = THREAD_NULL;
 }

}

/** setPriority(int tp)
  * sets the priority of the thread to "tp"
  * "tp" must be a valid priority defined in the
  * Thread class
**/
void Thread::setPriority(int tp)
{
 if(m_hThread == THREAD_NULL)
 {
  throw ThreadException("Thread object is null");
 }
 else
 {
#ifdef WIN32
  if(SetThreadPriority(m_hThread,tp) == 0)
  {
   throw ThreadException("Failed to set priority");
  }
#endif
 }
}

void Thread::setAffinity(unsigned cpu)
{
#ifdef WIN32
 DWORD mask=1;
 mask <<= cpu;
 if(SetThreadAffinityMask(m_hThread,mask)==0)
  throw ThreadException("Failed to set affinity");
#else
#ifdef HAVE_PTHREAD_SETAFFINITY_NP
 cpu_set_t cpuset;
 CPU_ZERO(&cpuset);
 CPU_SET(cpu, &cpuset);
#ifndef P2_PTHREAD_SETAFFINITY
 if(pthread_setaffinity_np(m_hThread, sizeof(cpuset), &cpuset)!=0)
  throw ThreadException("Failed to set affinity");
#else
 if(pthread_setaffinity_np(m_hThread, &cpuset)!=0)
  throw ThreadException("Failed to set affinity");
#endif
#else
 DISPLAY("Thread affinity not supported on this system")
#endif
#endif
 sleep(0);
}


/** suspend() 
  * suspends the thread
**/
void Thread::suspend()
{
 
 if(m_hThread == THREAD_NULL)
 {
  throw ThreadException(string("Thread object is null ->")+m_strName);
 }
 else
 {
#ifdef WIN32
  ASSIGN_BOOL(itsSuspendedFlag,true);
  if(SuspendThread(m_hThread) < 0)
  {
   throw ThreadException(string("Failed to suspend thread ->")+m_strName);
  }
#else
  TRACE("Tread suspended")

  pthread_mutex_lock(&m_hSuspendMutex);
  TRACE("Cond mutex count=" << m_hSuspendMutex.__m_count)
  TRACE("Cond lock status=" << m_hSuspendMutex.__m_lock.__status)
  itsSuspendedFlag=true;

  while(itsSuspendedFlag==true)
  {
   int ms=SUSPENDWAITMS;
   struct timespec abs_ts;
    struct timeval cur_tv;
   gettimeofday(&cur_tv, NULL);
   abs_ts.tv_sec = cur_tv.tv_sec + ms/1000;
   abs_ts.tv_nsec = (cur_tv.tv_usec + ms%1000*1000)*1000;

   // FIX by Steve Rodgers
   if(abs_ts.tv_nsec > 999999999)
   {
             abs_ts.tv_sec++;
                abs_ts.tv_nsec -= 1000000000;
            }
            // End fix
   
   pthread_cond_timedwait(&m_SuspendCondition,&m_hSuspendMutex,&abs_ts);
   TRACE("Cond condition lock status=" << m_SuspendCondition.__c_lock.__status)
   
   if(itsRunningFlag==false)
   {
    TRACE("Resume thread for shutdown cleanup")
    break;
   }
  }
  
  pthread_mutex_unlock(&m_hSuspendMutex);
  TRACE("Cond mutex count=" << m_hSuspendMutex.__m_count)
  TRACE("Cond lock status=" << m_hSuspendMutex.__m_lock.__status)

  TRACE("Thread resumed")
#endif
 }
}

/** resume() 
  * resumes a suspended thread
**/
void Thread::resume()
{
 
 if(m_hThread == THREAD_NULL)
 {
  throw ThreadException(string("Thread object is null ->")+m_strName);
 }
 else
 {
#ifdef WIN32
  if(ResumeThread(m_hThread) < 0)
  {
   
   throw ThreadException(string("Failed to resume thread ->")+m_strName);
  }
  else
  {
   ASSIGN_BOOL(itsSuspendedFlag,false);   
  }
#else
  pthread_mutex_lock(&m_hSuspendMutex);
  TRACE("Cond mutex count=" << m_hSuspendMutex.__m_count)
  TRACE("Cond lock status=" << m_hSuspendMutex.__m_lock.__status)
  itsSuspendedFlag=false;
  pthread_cond_signal(&m_SuspendCondition);
  TRACE("Cond condition lock status=" << m_SuspendCondition.__c_lock.__status)
  pthread_mutex_unlock(&m_hSuspendMutex);
  TRACE("Cond mutex count=" << m_hSuspendMutex.__m_count)
  TRACE("Cond lock status=" << m_hSuspendMutex.__m_lock.__status)
#endif
 }
}

/** wait(long ms) 
  * makes the thread suspend execution until the
  * mutex is released by another thread.
  * "ms" specifies a time-out for the wait operation.
  * "ms" defaults to 5000 milli-seconds
**/
bool Thread::wait(long ms)
{
 
 if(itsWorkingThreadID!=0)
 {
  
 }
 
#ifdef WIN32
 Sleep(0);
 EnterCriticalSection(&m_hMutex);
 ASSIGN_LONG(itsWorkingThreadID,threadID());
#else
 struct timespec abs_ts;
  struct timeval cur_tv;
 gettimeofday(&cur_tv, NULL);
 abs_ts.tv_sec = cur_tv.tv_sec + ms/1000;
 abs_ts.tv_nsec = (cur_tv.tv_usec + ms%1000*1000)*1000;
 
 int res=pthread_mutex_timedlock(&m_hMutex,&abs_ts); 
 TRACE("Mutex count=" << m_hMutex.__m_count)
 TRACE("Lock status=" << m_hMutex.__m_lock.__status)

 switch(res)
 {
  case 0:
   itsWorkingThreadID=threadID(); //++ v1.5
   TRACE("Thread::wait - end") 
   return true;
  case EINVAL:
   throw ThreadException(string("pthread_mutex_timedlock: Invalid value ->")+m_strName);
   break;
  case ETIMEDOUT:
   throw ThreadException(string("pthread_mutex_timedlock: Wait timed out ->")+m_strName);
   break;
  default:
   throw ThreadException(string("pthread_mutex_timedlock: Unexpected value ->")+m_strName);  
 }
#endif
 return false;
}

/** release() 
  * releases the mutex "m" and makes it
  * available for other threads
**/
void Thread::release()
{
 
#ifdef WIN32
 ASSIGN_LONG(itsWorkingThreadID,0L);
 LeaveCriticalSection(&m_hMutex);
#else
 pthread_mutex_unlock(&m_hMutex); 
 TRACE("Mutex count=" << m_hMutex.__m_count)
 TRACE("Lock status=" << m_hMutex.__m_lock.__status)
#endif
 
}

// ThreadException
ThreadException::ThreadException(const char* m)
{
 msg = m;
}

ThreadException::ThreadException(string m)
{
 msg = m;
}

string ThreadException::getMessage() const
{
 return msg;
}

// global thread callback
#ifdef WIN32
unsigned int _ou_thread_proc(void* param)
{
 
 try
 { 
  Thread* tp = (Thread*)param;
  tp->running();
  tp->run();
 }
 catch(...)
 {
  //DISPLAY("Unhandled exception in thread callback")
 }

  
 return 0;
}
#else
void* _ou_thread_proc(void* param)
{
 TRACE("Start _ou_thread_proc") 
 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
 try
 {
  Thread* tp = (Thread*)param;
  tp->running();
  TRACE("Thread " << tp->getName() << " started")
  tp->run();
 }
 catch(...)
 {
  DISPLAY("Unhandled exception in thread callback")
 }

 TRACE("End _ou_thread_proc")
 pthread_exit(NULL);
 return NULL;
}
#endif

/××××××××××××××Thread.cpp×××××××××××××××/


/**************************RunTimeLog.h*******************************/


#ifndef __RUNTIMELOGG__
#define __RUNTIMELOGG__

#include
using namespace std;
#include
#include
#include "Thread.h"

class LogInfo
{
public:
 enum LogLevel { INFO,WARNING,CRITICAL,DEBUG };

protected:
 string itsLog; //v1.4
 string itsFile; //v1.4
 int itsLine;
 enum LogLevel itsLevel;
 string itsInstance;

public: 
 LogInfo(char* theLog);
 LogInfo(const char* theLog);
 LogInfo(char* theLog,const char* theFile,int theLine,enum LogLevel theLevel,const char* theInstance=NULL);
 LogInfo(const char* theLog,const char* theFile,int theLine,enum LogLevel theLevel,const char* theInstance=NULL);
 virtual ~LogInfo();
 virtual const char* get() { return itsLog.c_str(); };
 virtual const char* getFile() { return itsFile.c_str(); };
 virtual int getLine() { return itsLine; };
 virtual enum LogLevel getLevel() { return itsLevel; };
 virtual void toStream(ostream& theStream);
 virtual void WriteLog(FILE*);
};

class RunTimeLog : public Thread
{
protected:
 static RunTimeLog* itsDefaultLogger;
 //ofstream itsStream;
 FILE*  m_file;
  
public:
 RunTimeLog(const char* theLoggerName);
 virtual ~RunTimeLog();
 //static void startDefaultLogger(const char* theFileName = NULL); // ++  v1.5
 static void postToDefaultLogger(LogInfo* theMessage);
 static void waitForCompletion();
 static void switchLogFile();

 void switchFile();
 void post(LogInfo* theMessage);
 void close();
 LogInfo * pop();

 virtual void run();
 
protected:
 RunTimeLog();
private:
 list m_lstLogInf;
};

#ifndef SILENT
#define DEBUG(a) \
 RunTimeLog::post(new LogInfo(a,__FILE__,__LINE__, LogMessage::DEBUG));
#else
#define DEBUG(a) ;
#endif

#define LOG(a) \
 RunTimeLog::postToDefaultLogger(new LogInfo(a,__FILE__,__LINE__, LogInfo::INFO));
#define WARNING(a) \
 RunTimeLog::postToDefaultLogger(new LogInfo(a,__FILE__,__LINE__, LogInfo::WARNING));
#define CRITICAL(a) \
 RunTimeLog::postToDefaultLogger(new LogInfo(a,__FILE__,__LINE__, LogInfo::CRITICAL));
/*#define STARTLOGGER(a) \
 RunTimeLog::startDefaultLogger(a);*/
#define STOPLOGGER() \
 RunTimeLog::waitForCompletion();
#define SWITCHLOGFile \
 RunTimeLog::switchLogFile();
#endif

/**************************RunTimeLog.h*******************************/

/******************************RunTimeLog.cpp************************************/


#define SILENT
#include "RunTimeLog.h"
#include
#include

RunTimeLog* RunTimeLog::itsDefaultLogger=NULL;

RunTimeLog * getRunLogInstance()
{
 static RunTimeLog gRunLog("DefaultLogger");
 return &gRunLog;
}
LogInfo::LogInfo(char* theLog)
           :itsLog(theLog), itsFile(NULL),itsLine(0), itsLevel(INFO)
{
}

LogInfo::LogInfo(const char* theLog)
           :itsLog(theLog), itsFile(""),itsLine(0), itsLevel(INFO)
{

}

LogInfo::LogInfo(char* theLog,const char* theFile,int theLine,enum LogLevel theLevel,const char* theInstance)
          :itsLog(theLog),itsFile(theFile), itsLine(theLine), itsLevel(theLevel)
{  

 if(theInstance!=NULL)
  itsInstance=theInstance+string("@");
}
 
LogInfo::LogInfo(const char* theLog,const char* theFile,int theLine,enum LogLevel theLevel,const char* theInstance)
        :itsLog(theLog),itsFile(theFile), itsLine(theLine), itsLevel(theLevel)
{  

 if(theInstance!=NULL)
  itsInstance=theInstance+string("@");
}

LogInfo::~LogInfo()
{

}

void LogInfo::toStream(ostream& theStream)
{
 char aTimeString[40];
 time_t aTime=time(NULL);
 strftime(aTimeString,sizeof(aTimeString),"[%Y-%m-%d %H:%M:%S]", localtime(&aTime));  
 theStream << aTimeString;
 
 switch(getLevel())
 {
  case LogInfo::WARNING:
   theStream << " [WARN] ";
   break;
  
  case LogInfo::CRITICAL:
   theStream << " [CRIT] ";
   break;
  
  case LogInfo::DEBUG:
   theStream << " [DEBG] ";
   break;
     
  default:
   theStream << " [INFO] ";   
 }
 
 theStream << itsFile << "(" << itsInstance << itsLine << "): " << itsLog << endl;
}

void LogInfo::WriteLog(FILE* p_file)
{  
 string temp;
 if (p_file == NULL)
 {
       return;
 }
 char aTimeString[40];
 time_t aTime=time(NULL);
 strftime(aTimeString,sizeof(aTimeString),"[%Y-%m-%d %H:%M:%S]", localtime(&aTime));  
 fprintf(p_file,aTimeString);

 switch(getLevel())
 {
 case LogInfo::WARNING:
  temp = " [WARN] ";
  break;

 case LogInfo::CRITICAL:
  temp = " [CRIT] ";
  break;

 case LogInfo::DEBUG:
  temp = " [DEBG] ";
  break;

 default:
  temp = " [INFO] ";   
 }

 //theStream << itsFile << "(" << itsInstance << itsLine << "): " << itsLog << endl;
 fprintf(p_file,"%s%s(%s%d):%s\n", temp.c_str(), itsFile.c_str(), itsInstance.c_str(), itsLine, itsLog.c_str());
 
}

RunTimeLog::RunTimeLog()
      :Thread("DefaultLogger")
{  
 start();
 //itsStream.open("messages.log");
}

RunTimeLog::RunTimeLog(const char* theLoggerName)
   :Thread(theLoggerName)
{  
 char aTimeString[40];
 time_t aTime=time(NULL);
 strftime(aTimeString,sizeof(aTimeString),"%Y%m%d", localtime(&aTime));

 char logName[50];
 memset(logName, '\0', sizeof(logName));
 sprintf(logName, ".\\RTimelog\\%s.log", aTimeString);
 _mkdir("RTimelog");

 //itsStream.open(logName, fstream::in | fstream::out | fstream::app);
    m_file = fopen(logName, "a");

 start();
}
 
RunTimeLog::~RunTimeLog()

 
 wait();
 //itsStream.close();
 if(m_file != NULL)
 {
  fclose(m_file);
 }
 release();

 shutdownInProgress();
}


void RunTimeLog::postToDefaultLogger(LogInfo* theMessage)
{
 
 if(isShuttingDown()) //++ v1.5
 {
  delete theMessage;
  return;
 }
 
 getRunLogInstance()->post(theMessage);
 /*if(itsDefaultLogger==NULL)
  itsDefaultLogger=new RunTimeLog();
 if(theMessage)
  itsDefaultLogger->post(theMessage);*/

void RunTimeLog::waitForCompletion()
{
 /*if(itsDefaultLogger!=NULL)
 {
  if(!isShuttingDown())  
   delete itsDefaultLogger;
  itsDefaultLogger=NULL;
 }*/
 getRunLogInstance()->close();
}

void RunTimeLog::switchLogFile()
{
    getRunLogInstance()->switchFile();
}
void RunTimeLog::close()
{
 wait();
 //itsStream.close();
 if(m_file != NULL)
 {
  fclose(m_file);
 }
 release();
}

void RunTimeLog::switchFile()
{
 close();
 char aTimeString[40];
 time_t aTime=time(NULL);
 strftime(aTimeString,sizeof(aTimeString),"%Y%m%d", localtime(&aTime));
 char logName[50];
 memset(logName, '\0', sizeof(logName));
 sprintf(logName, ".\\RTimelog\\%s.log", aTimeString);
 _mkdir("RTimelog");

 wait();
 //itsStream.open(logName, fstream::in | fstream::out | fstream::app);
    m_file = fopen(logName, "a");
 release();
}
void RunTimeLog::post(LogInfo* theMessage)
{
 if(isShuttingDown()) //++ v1.5
 {
  delete theMessage;
  return;
 }

 wait();
    m_lstLogInf.push_back(theMessage);
 if(isSuspended()==true)
 {
  resume();
 }
 release();
}

LogInfo * RunTimeLog::pop()
{
    if (m_lstLogInf.size() == 0)
    {
  return NULL;
    }

 LogInfo * p_loginf = m_lstLogInf.front();

 m_lstLogInf.pop_front();

 return p_loginf;
}
void RunTimeLog::run()
{
 while(true)
  {   
    if(m_hThread!=0)
    {  
     try
     {         
      while(true)
      {
            
        wait();     

        if(m_lstLogInf.size() == 0)
        {
         release();
         break;
        }
        
        LogInfo* aMessage=pop();

        if (aMessage == NULL)
        {
                                    release();
         break;
        }
        release(); 
        //aMessage->toStream(itsStream);
        aMessage->WriteLog(m_file);
        delete aMessage;
      }        
       suspend();  
     }
     catch(ThreadException& ex)
     {
      release();
     }
     catch(...)
     {
      release();
     }   
    } 
  }
}

/******************************RunTimeLog.cpp************************************/





你可能感兴趣的:(多线程下面日志输出-线程安全-消息队列循环输出)