// Solution 2 // (note: compiles with gcc 3.2, VC6) #include "CriticalSection.h" #include "message_handler_log.h" #include <queue> // the object to be started - on a given thread struct win32_thread_obj { virtual ~win32_thread_obj() {} virtual void operator()() = 0; }; struct win32_thread_manager { typedef win32_thread_obj thread_obj_base; static void sleep( int nMillisecs) { Sleep( nMillisecs); } static void create_thread( win32_thread_obj & obj) { DWORD dwThreadID; CreateThread( 0, 0, win32_thread_manager::ThreadProc, &obj, 0, &dwThreadID); } private: static DWORD WINAPI ThreadProc( LPVOID lpData) { win32_thread_obj * pThread = ( win32_thread_obj *)lpData; ( *pThread)(); return 0; } }; // allows thread-safe writing template< class char_type, class traits_type = std::char_traits< char_type> > class thread_safe_log_writer { typedef thread_safe_log_writer< char_type, traits_type> this_class; typedef std::basic_ostream< char_type, traits_type> ostream_type; typedef std::basic_string< char_type, traits_type> string_type; // forward declaration struct thread_info; friend struct thread_info; // thread-related definitions typedef win32_thread_manager thread_manager; typedef typename thread_manager::thread_obj_base thread_obj_base; // so that from our thread we know the object we're manipulating struct thread_info : public thread_obj_base { thread_info() : m_bHasFinished( false), m_pThis( NULL) {} /* virtual */ void operator()() { while ( true) { std::string * pstr = NULL; { CAutoLockUnlock locker( m_pThis->m_cs); // get the string if ( m_pThis->m_astrMessages.size() > 0) { pstr = m_pThis->m_astrMessages.front(); m_pThis->m_astrMessages.pop(); } // ... only when there are no more messages, // will we ask if we should be destructed else if ( m_pThis->m_bShouldBeDestructed) { // signal to the other thread we've finished m_bHasFinished = true; return; } } // write the string if ( pstr) { m_pThis->m_underlyingLog << *pstr; delete pstr; } else // nothing to write - wait thread_manager::sleep( 1); } } this_class * m_pThis; volatile bool m_bHasFinished; }; public: void add_message( const string_type & str) { CAutoLockUnlock locker( m_cs); m_astrMessages.push( new string_type( str)); } thread_safe_log_writer( ostream_type & underlyingLog) : m_underlyingLog( underlyingLog), m_bShouldBeDestructed( false) { m_info.m_pThis = this; thread_manager::create_thread( m_info); } ~thread_safe_log_writer() { // signal to the other thread we're about to be // destructed { CAutoLockUnlock locker( m_cs); m_bShouldBeDestructed = true; } // wait while the other thread writes all messages while ( true) { CAutoLockUnlock locker( m_cs); if ( m_info.m_bHasFinished) // the other thread has finished break; } } CCriticalSection & cs() const { return m_cs; } private: // the critical section used for thread-safe locking mutable CCriticalSection m_cs; // needed to create the other thread thread_info m_info; volatile bool m_bShouldBeDestructed; ostream_type & m_underlyingLog; std::queue< string_type*> m_astrMessages; }; // forward declaration template< class char_type, class traits_type = std::char_traits< char_type> > class basic_thread_safe_log; template< class char_type, class traits_type = std::char_traits< char_type> > class basic_internal_thread_safe_log { typedef std::basic_ostream< char_type, traits_type> ostream_type; friend class basic_thread_safe_log< char_type, traits_type>; // non-copyiable typedef basic_internal_thread_safe_log< char_type, traits_type> this_class; basic_internal_thread_safe_log( const this_class &); this_class & operator=( this_class &); public: basic_internal_thread_safe_log( ostream_type & underlyingLog) : m_underlyingLog( underlyingLog), m_writer( underlyingLog) {} ~basic_internal_thread_safe_log() {} void write_message( const std::basic_string< char_type, traits_type> & str) { m_writer.add_message( str); } void copy_state_to( ostream_type & dest) const { CAutoLockUnlock locker( m_writer.cs()); dest.copyfmt( m_underlyingLog); dest.setstate( m_underlyingLog.rdstate()); } void copy_state_from( const ostream_type & src) { CAutoLockUnlock locker( m_writer.cs()); m_underlyingLog.copyfmt( src); m_underlyingLog.setstate( m_underlyingLog.rdstate()); } private: ostream_type & m_underlyingLog; thread_safe_log_writer< char_type, traits_type> m_writer; }; typedef basic_internal_thread_safe_log< char> internal_thread_safe_log; typedef basic_internal_thread_safe_log< wchar_t> winternal_thread_safe_log; template< class char_type, class traits_type> class basic_thread_safe_log // *** protected, not public !!! : protected basic_message_handler_log< char_type, traits_type> { typedef std::basic_ostream< char_type, traits_type> ostream_type; typedef basic_internal_thread_safe_log< char_type, traits_type> internal_type; public: basic_thread_safe_log( internal_type & tsLog) : m_tsLog( tsLog) { // get underlying stream state tsLog.copy_state_to( ts() ); } basic_thread_safe_log( const basic_thread_safe_log< char_type, traits_type> & from) : m_tsLog( from.m_tsLog), // ... on some platforms, a std::ostream base copy-constructor // might be defined as private... basic_message_handler_log< char_type, traits_type>() { // get underlying stream state m_tsLog.copy_state_to( ts() ); } ~basic_thread_safe_log() { // copy state to underlying stream m_tsLog.copy_state_from( ts() ); } // get base class - to which we can write std::basic_ostream< char_type, traits_type> & ts() { return *this; } protected: virtual void on_new_message( const string_type & str) { m_tsLog.write_message( str); } private: internal_type & m_tsLog; }; typedef basic_thread_safe_log< char> thread_safe_log; typedef basic_thread_safe_log< wchar_t> wthread_safe_log; ////////////////////////////////////////////////////////// // Test #include <iostream> #include <fstream> #include <iomanip> const int THREADS_COUNT = 200; const int WRITES_PER_THREAD = 500; thread_safe_log get_log() { static std::ofstream out( "out.txt"); static internal_thread_safe_log log( out); return thread_safe_log( log); } LONG nRemainingThreads = THREADS_COUNT; DWORD WINAPI WriteToLog( LPVOID lpData) { int *pnThreadID = ( int *)lpData; // wait for all threads to be created, so that // we write at about the same time (stress it ;-)) Sleep( 500); for ( int idx = 0; idx < WRITES_PER_THREAD; idx++) { get_log().ts() << "writing double: " << 5.23 << std::endl; get_log().ts() << "message " << idx << " from thread " << *pnThreadID << std::endl; // ... get other threads a chance to write Sleep( 1); if ( ( idx == 10) && ( *pnThreadID == 10)) { // from now on, '5.23' will be written as '5,23' // (german locale) std::locale loc = std::locale( "german"); get_log().ts().imbue( loc); } } InterlockedDecrement( &nRemainingThreads); delete pnThreadID; return 0; } int main(int argc, char* argv[]) { // make sure the statics are initialized get_log(); for ( int idx = 0; idx < THREADS_COUNT; ++idx) { DWORD dwThreadID; CreateThread( 0, 0, WriteToLog, new int( idx), 0, &dwThreadID); } // wait for all threads to end while ( true) { InterlockedIncrement( &nRemainingThreads); if ( InterlockedDecrement( &nRemainingThreads) == 0) break; Sleep( 100); } return 0; } |