|
|
|
|
||||||
![]() |
|
|
LinkBack | Outils de la discussion |
|
|
#1 |
|
Messages: n/a
Hébergeur: |
Here is a skeleton of how I handle pthreads (the same pattern works with
win32 threads) in C++; Ther is of course a lot more to write for the thread management itself but the importants things are there. I use it by writting a derived class that overload the run() method. Hope it s. class Thread { static void* glue( void* ); private: pthread_t tid; pthread_attr_t attr; public: Thread(); virtual void* run() = 0; }; void* Thread::glue( void* t ) { return ((Thread*)t)->run(); } Thread::Thread() { // ... pthread_create( &this->tid, &this->attr, Thread::glue, (void*)this ); // ... } |
|
|
|
#2 |
|
Messages: n/a
Hébergeur: |
Laurent D.A.M. MENTEN wrote:
> Here is a skeleton of how I handle pthreads (the same pattern works with > win32 threads) in C++; Ther is of course a lot more to write for the > thread management itself but the importants things are there. I use it > by writting a derived class that overload the run() method. > > Hope it s. > > class Thread > { > static void* glue( void* ); > > private: > pthread_t tid; > pthread_attr_t attr; > > public: > Thread(); > > virtual void* run() = 0; > }; > > void* Thread::glue( void* t ) > { > return ((Thread*)t)->run(); > } > > Thread::Thread() > { > // ... > pthread_create( &this->tid, &this->attr, Thread::glue, (void*)this ); > // ... > } That code has a major race condition issue. Never invoke the thread on a virtual function until you can guarantee the the object is fully constructed. Some argue that it's sufficient to "unleash" the thread at the most derived constructor and wait for it's termination at the most derived destructor, some argue that you can't unleash the thread until the constructor has returned and that you can't even call delete until the thread has been terminated. The code below shows how to create a thread object (in this case called Task). This is similar to the Austria C++ code however the Austria C++ code is portable across Win32 and Pthreads. Use Austria or boost threads. You'll see that it's similar to your code but the virtual method is not called until after the the "start" method is called. #include <ctime> #include <algorithm> #include <cmath> #include <pthread.h> namespace tc { // All the posix thread stuff goes here - API conforms to Austria C++ // ======== MutexAttr ================================================= class MutexAttr { public: MutexAttr( int i_kind ) { pthread_mutexattr_init( m_attr ); if ( pthread_mutexattr_settype( m_attr, i_kind ) != 0 ) { abort(); } } ~MutexAttr() { pthread_mutexattr_destroy( m_attr ); } pthread_mutexattr_t * GetAttr() { return m_attr; } pthread_mutexattr_t m_attr[ 1 ]; }; MutexAttr g_MA_Fast( PTHREAD_MUTEX_FAST_NP ); MutexAttr g_MA_Recursive( PTHREAD_MUTEX_RECURSIVE_NP ); MutexAttr g_MA_Check( PTHREAD_MUTEX_ERRORCHECK_NP ); // ======== Mutex ================================================== === class Conditional; class Mutex { public: friend class Conditional; // ======== MutexType ============================================= enum MutexType { NonRecursive, Recursive, Checking }; // ======== Mutex ================================================= Mutex( MutexType i_type = NonRecursive ) { pthread_mutex_t * l_mutex = m_mutex_context.m_data; switch ( i_type ) { case NonRecursive : { int l_result = pthread_mutex_init( l_mutex, g_MA_Fast.GetAttr() ); if ( l_result != 0 ) { abort(); } break; } case Recursive : { int l_result = pthread_mutex_init( l_mutex, g_MA_Recursive.GetAttr() ); if ( l_result != 0 ) { abort(); } break; } case Checking : { int l_result = pthread_mutex_init( l_mutex, g_MA_Check.GetAttr() ); if ( l_result != 0 ) { abort(); } break; } default : { abort(); } } } // ======== Mutex ================================================= virtual ~Mutex() { pthread_mutex_t * l_mutex = m_mutex_context.m_data; int l_result = pthread_mutex_destroy( l_mutex ); if ( l_result != 0 ) { // trying to destroy a mutex that is locked abort(); } } // ======== Lock ================================================== void Lock() { pthread_mutex_t * l_mutex = m_mutex_context.m_data; int l_result = pthread_mutex_lock( l_mutex ); if ( l_result != 0 ) { if ( l_result == EINVAL ) { abort(); } if ( l_result == EDEADLK ) { abort(); } abort(); } } // ======== TryLock =============================================== bool TryLock() { pthread_mutex_t * l_mutex = m_mutex_context.m_data; int l_result = pthread_mutex_trylock( l_mutex ); if ( EBUSY == l_result ) { return false; } if ( l_result != 0 ) { if ( l_result == EINVAL ) { abort(); } abort(); } return true; } // ======== Unlock ================================================ void Unlock() { pthread_mutex_t * l_mutex = m_mutex_context.m_data; int l_result = pthread_mutex_unlock( l_mutex ); if ( l_result != 0 ) { if ( l_result == EINVAL ) { abort(); } if ( l_result == EPERM ) { abort(); } abort(); } } struct MutexContext { pthread_mutex_t m_data[1]; }; private: /** * m_mutex_context is a system dependant context variable. */ MutexContext m_mutex_context; // copy constructor and assignment operator are private and // unimplemented. It is illegal to copy a mutex. Mutex( const Mutex & ); Mutex & operator= ( const Mutex & ); }; // ======== Conditional =============================================== // condition variable wrapper class Conditional { public: // ======== Conditional =========================================== Conditional( Mutex & i_mutex ) : m_mutex( i_mutex.m_mutex_context.m_data ) { int l_result = pthread_cond_init( m_cond, static_cast<const pthread_condattr_t *>( 0 ) ); if ( l_result != 0 ) { abort(); } } // destructor virtual ~Conditional() { int l_result = pthread_cond_destroy( m_cond ); if ( l_result != 0 ) { abort(); } } // ======== Wait ================================================== void Wait() { int l_result = pthread_cond_wait( m_cond, m_mutex ); if ( l_result != 0 ) { abort(); } } // ======== Post ================================================== void Post() { int l_result = pthread_cond_signal( m_cond ); if ( l_result != 0 ) { abort(); } } // ======== PostAll =============================================== void PostAll() { int l_result = pthread_cond_broadcast( m_cond ); if ( l_result != 0 ) { abort(); } } private: pthread_mutex_t * m_mutex; pthread_cond_t m_cond[ 1 ]; // copy constructor and assignment operator are private and // unimplemented. It is illegal to copy a Conditional. Conditional( const Conditional & ); Conditional & operator= ( const Conditional & ); }; // ======== ConditionalMutex ========================================== class ConditionalMutex : public Mutex, public Conditional { public: // ======== ConditionalMutex ====================================== ConditionalMutex( MutexType i_type = NonRecursive ) : Mutex( i_type ), Conditional( * static_cast< Mutex * >( this ) ) { } virtual ~ConditionalMutex() {} private: ConditionalMutex( const ConditionalMutex & ); ConditionalMutex & operator= ( const ConditionalMutex & ); }; // ======== Lock ================================================ template <typename w_MutexType> class Lock { public: w_MutexType & m_mutex; Lock( w_MutexType & io_mutex ) : m_mutex( io_mutex ) { m_mutex.Lock(); } ~Lock() { m_mutex.Unlock(); } void Wait() { return m_mutex.Wait(); } void Post() { m_mutex.Post(); } void PostAll() { m_mutex.PostAll(); } private: // must not allow copy or assignment so make // these methods private. Lock( const Lock & ); Lock & operator=( const Lock & ); }; // ======== Unlock ============================================= template <typename w_MutexType> class Unlock { public: w_MutexType & m_mutex; Unlock( w_MutexType & io_mutex ) : m_mutex( io_mutex ) { m_mutex.Unlock(); } ~Unlock() { m_mutex.Lock(); } private: // must not allow copy or assignment so make // these methods private. Unlock( const Unlock & ); Unlock & operator=( const Unlock & ); }; // ======== TryLock ================================================== = template< typename w_MutexType > class TryLock { w_MutexType & m_mutex; bool m_is_acquired; public: TryLock( w_MutexType & io_mutex ) : m_mutex( io_mutex ), m_is_acquired( false ) { m_is_acquired = m_mutex.TryLock(); } inline ~TryLock() { if ( m_is_acquired ) { m_mutex.Unlock(); } } void SetAquired( bool i_is_acquired ) { m_is_acquired = i_is_acquired; } bool IsAcquired() const { return m_is_acquired; } private: /* Unimplemented. */ TryLock( const TryLock & ); TryLock & operator=( const TryLock & ); }; // ======== Task ================================================== ==== class Task { public: typedef int TaskID; // ======== Task ================================================== Task() : m_started( false ), m_completed( false ), m_is_joined( false ) { int l_result = pthread_create( & m_thread_id, static_cast<const pthread_attr_t *>( 0 ), & start_routine, static_cast<void *>( this ) ); if ( 0 != l_result ) { abort(); } } // ======== ~Task ================================================= virtual ~Task() { Wait(); } // ======== Work ================================================== virtual void Work() = 0; // ======== Start ================================================= void Start() { if ( ! m_started ) { // Wake this thread Lock<ConditionalMutex> l_lock( m_thread_cond_mutex ); m_started = true; l_lock.Post(); } } // ======== Wait ================================================== void Wait() { if ( ! m_is_joined ) { // Wait here to be started Lock<ConditionalMutex> l_lock( m_wait_cond_mutex ); while ( ! m_completed ) { l_lock.Wait(); } // Need to call join here ... if ( ! m_is_joined ) { m_is_joined = true; void * l_return_value; int l_result = pthread_join( m_thread_id, & l_return_value ); if ( 0 != l_result ) { abort(); } } } // l_lock is unlocked here } // ======== GetThisId ============================================= TaskID GetThisId() { return m_thread_id; } // ======== GetSelfId ============================================= static TaskID GetSelfId() { return ::pthread_self(); } private: // // Can't copy a task. Task( const Task & ); Task & operator= ( const Task & ); pthread_t m_thread_id; volatile bool m_started; volatile bool m_completed; volatile bool m_is_joined; ConditionalMutex m_thread_cond_mutex; ConditionalMutex m_wait_cond_mutex; static void * start_routine( void * i_task ) { Task * l_this_task = static_cast<Task *>( i_task ); { // Wait here to be started Lock<ConditionalMutex> l_lock( l_this_task->m_thread_cond_mutex ); while ( ! l_this_task->m_started ) { l_lock.Wait(); } } // do the work ... l_this_task->Work(); { // Wake all the waiters. Lock<ConditionalMutex> l_lock( l_this_task->m_wait_cond_mutex ); l_this_task->m_completed = true; l_lock.PostAll(); } return 0; } }; // ======== Barrier ================================================== = class Barrier { public: Barrier( unsigned i_thread_count, ConditionalMutex & i_cond_mutex ) : m_thread_count( i_thread_count ), m_cond_mutex( i_cond_mutex ), m_count() { } unsigned Enter() { unsigned l_num; Lock<ConditionalMutex> l_lock( m_cond_mutex ); l_num = m_count ++; if ( ( m_thread_count - 1 ) == l_num ) { l_lock.PostAll(); } else { l_lock.Wait(); } return l_num; } unsigned m_thread_count; ConditionalMutex & m_cond_mutex; volatile unsigned m_count; }; } // namespace tc |
|
|
|
#3 |
|
Messages: n/a
Hébergeur: |
Laurent D.A.M. MENTEN wrote:
> Here is a skeleton of how I handle pthreads (the same pattern works with > win32 threads) in C++; Ther is of course a lot more to write for the > thread management itself but the importants things are there. I use it > by writting a derived class that overload the run() method. > > Hope it s. > > class Thread > { > static void* glue( void* ); > > private: > pthread_t tid; > pthread_attr_t attr; > > public: > Thread(); > > virtual void* run() = 0; > }; > > void* Thread::glue( void* t ) > { > return ((Thread*)t)->run(); > } > > Thread::Thread() > { > // ... > pthread_create( &this->tid, &this->attr, Thread::glue, (void*)this ); > // ... > } This is wrong, Thread::glue is not an extern "C" function, so it shouldn't be passed to pthread_create. You might get away with it (I'd expect at least a complier warning), but it isn't portable. I don't see why people are so hung up about passing a free function to pthread_create in C++. -- Ian Collins. |
|
|
|
#4 |
|
Messages: n/a
Hébergeur: |
On Oct 15, 1:01 am, Gianni Mariani <gi4nos...@marian.ws> wrote:
> Laurent D.A.M. MENTEN wrote: > > Here is a skeleton of how I handle pthreads (the same pattern works with > > win32 threads) in C++; Ther is of course a lot more to write for the > > thread management itself but the importants things are there. I use it > > by writting a derived class that overload the run() method. > > Hope it s. > > class Thread > > { > > static void* glue( void* ); > > private: > > pthread_t tid; > > pthread_attr_t attr; > > public: > > Thread(); > > virtual void* run() = 0; > > }; > > void* Thread::glue( void* t ) > > { > > return ((Thread*)t)->run(); > > } > > Thread::Thread() > > { > > // ... > > pthread_create( &this->tid, &this->attr, Thread::glue, (void*)this ); > > // ... > > } > That code has a major race condition issue. You're right about the race condition, of course, and I doubt that his code would work on a modern system (dual core or more). But it won't even compile with a conformant C++ compiler. His function glue must be `extern "C"', and thus cannot be a member. (On the other hand, it *can* be static, in order to avoid all risk of name collision.) > Never invoke the thread on > a virtual function until you can guarantee the the object is fully > constructed. Some argue that it's sufficient to "unleash" the thread at > the most derived constructor and wait for it's termination at the most > derived destructor, some argue that you can't unleash the thread until > the constructor has returned and that you can't even call delete until > the thread has been terminated. I'm not sure what the difference is, but globally, if you're trying to deal with Thread objects: -- The obvious solution is to use the strategy pattern, rather than the template method solution. Move the virtual function run() over into a separate ThreadExec interface, and pass a ThreadExec* to the constructor of Thread. Depending on your goals, you could specify transfer of ownership (with the destructor of Thread deleting it) or not. -- Personally, I don't like the idea of a starting a thread in the constructor anyway. But it's really just a vague uneasy feeling, which I can't really justify if the strategy pattern is used. If the constructor doesn't start the thread, then there's no problem with using the template method pattern, however. -- With regards to lifetime: obviously, any "object" must continue to live as long as any code references members in it. (This can be a serious consideration if you are starting a detached thread.) > The code below shows how to create a thread object (in this > case called Task). This is similar to the Austria C++ code > however the Austria C++ code is portable across Win32 and > Pthreads. Use Austria or boost threads. You'll see that it's > similar to your code but the virtual method is not called > until after the the "start" method is called. At the cost of an additional lock/conditional variable. This is the solution adopted in Boost threads as well, for different reasons. Boost copies the functional object, and needs the conditional to ensure that the copy is finished. In this case, the additional lock is an acceptable cost in order to allow transparent use of local, or even temporary, functional objects. If you're not doing this, i.e. you require derivation from a Thread (or ThreadExec) class, and you require the objects lifetime to extend to the end of the thread, there's no point in it. My own experience is that detached threads and non detached threads are really two different things, and require different solutions. For detached threads, I don't use a Thread object at all---just a function to start the thread, which works more or less like the Boost threads, copying the functional object into the new thread, and synchronizing to avoid returning from the function until the copy has finished. For non-detached threads, on the other hand, I use something like what the original poster proposed, but with the strategy pattern rather than the template method pattern---the return value from joining is a pointer to the ThreadExec object, so you can easily recover any values the thread may have calculated, and delete it, if it was originally dynamically allocated. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#5 |
|
Messages: n/a
Hébergeur: |
> You're right about the race condition, of course, and I doubt
> that his code would work on a modern system (dual core or more). > But it won't even compile with a conformant C++ compiler. His > function glue must be `extern "C"', and thus cannot be a member. > (On the other hand, it *can* be static, in order to avoid all > risk of name collision.) Anyway, it does compile (gcc 3.3.5 + linux 2.6.11 + x86_64) and it does its work pretty well for my own personal needs. I never claimed it was complete or portable, nor did I claim it was conformant and I will never claim it is the best and only implementation. The point is I answered a simple question with simplicity. My skeleton allows Mr. JackC to work with thread and objects in an easy way, further more I sticked to the logic I (believe I have) found in the code sample in the original post and tried my best to Mr. JackC understand mine. It is a shame so many people here are not able to understand not everyone in this world is a C++ guru or theorician. |
|
|
|
#6 |
|
Messages: n/a
Hébergeur: |
Laurent D.A.M. MENTEN wrote:
>> You're right about the race condition, of course, and I doubt >> that his code would work on a modern system (dual core or more). >> But it won't even compile with a conformant C++ compiler. His >> function glue must be `extern "C"', and thus cannot be a member. >> (On the other hand, it *can* be static, in order to avoid all >> risk of name collision.) > > Anyway, it does compile (gcc 3.3.5 + linux 2.6.11 + x86_64) and it does > its work pretty well for my own personal needs. I never claimed it was > complete or portable, nor did I claim it was conformant and I will never > claim it is the best and only implementation. > It's more a case of it being a dangerous solution. There is every chance the thread will run before the object is constructed on a multi-core system, or even a single code one if the new thread gets to run first. > The point is I answered a simple question with simplicity. My skeleton > allows Mr. JackC to work with thread and objects in an easy way, further > more I sticked to the logic I (believe I have) found in the code sample > in the original post and tried my best to Mr. JackC understand mine. > > It is a shame so many people here are not able to understand not > everyone in this world is a C++ guru or theorician. There is nothing theoretical about race conditions or incorrect parameter types. -- Ian Collins. |
|
|
|
#7 |
|
Messages: n/a
Hébergeur: |
> It's more a case of it being a dangerous solution. There is every
> chance the thread will run before the object is constructed on a > multi-core system, or even a single code one if the new thread gets to > run first. I will repeat myself once more, the code I posted is in NO WAY complete, so do not assume it to be useable as-is. I just took the essence of the way I do things so that Mr. JackC can use with ease. |
|
|
|
#8 |
|
Messages: n/a
Hébergeur: |
On Oct 15, 3:58 pm, "Laurent D.A.M. MENTEN"
<laurent.men...@teledisnet.be> wrote: > > You're right about the race condition, of course, and I doubt > > that his code would work on a modern system (dual core or more). > > But it won't even compile with a conformant C++ compiler. His > > function glue must be `extern "C"', and thus cannot be a member. > > (On the other hand, it *can* be static, in order to avoid all > > risk of name collision.) > Anyway, it does compile (gcc 3.3.5 + linux 2.6.11 + x86_64) This is a known bug in g++, which will presumably be fixed in a future release. At which point, your code will stop compiling. [...] > It is a shame so many people here are not able to understand not > everyone in this world is a C++ guru or theorician. It's a shame so many people don't care about writing correct code. Gianni pointed out the fact that your code simply isn't correct from a threading point point of view. (His point was in fact considerably more important than mine.) Your code doesn't work reliably under Windows, Linux or Solaris. I don't know about other systems, but I'm pretty sure that it doesn't work under any Posix system. Gianni also pointed out a workable solution---I personally find using the strategy pattern simpler for non-detached threads, but his solution also works in that case as well. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#9 |
|
Messages: n/a
Hébergeur: |
> This is a known bug in g++, which will presumably be fixed in a
> future release. At which point, your code will stop compiling. Checked this morning, still not fixed in 4.2.2... |
|
|
|
#10 |
|
Messages: n/a
Hébergeur: |
"James Kanze" <james.kanze@gmail.com> wrote in message
news:1192521490.065199.127910@v29g2000prd.googlegr oups.com... On Oct 15, 3:58 pm, "Laurent D.A.M. MENTEN" <laurent.men...@teledisnet.be> wrote: >> > You're right about the race condition, of course, and I doubt >> > that his code would work on a modern system (dual core or more). >> > But it won't even compile with a conformant C++ compiler. His >> > function glue must be `extern "C"', and thus cannot be a member. >> > (On the other hand, it *can* be static, in order to avoid all >> > risk of name collision.) >> Anyway, it does compile (gcc 3.3.5 + linux 2.6.11 + x86_64) >This is a known bug in g++, which will presumably be fixed in a >future release. At which point, your code will stop compiling. > [...] >> It is a shame so many people here are not able to understand not >> everyone in this world is a C++ guru or theorician. >It's a shame so many people don't care about writing correct >code. >[...] Not sure why C++ threading has to be overally complicated... I mean, why not something simple like: http://appcore.home.comcast.net/misc...hread_cpp.html ??? |
|
|
|
#11 |
|
Messages: n/a
Hébergeur: |
On Oct 16, 8:23 pm, "Laurent D.A.M. MENTEN"
<laurent.men...@teledisnet.be> wrote: > > This is a known bug in g++, which will presumably be fixed in a > > future release. At which point, your code will stop compiling. > Checked this morning, still not fixed in 4.2.2... So what's your point? Since it doesn't affect correct code, I doubt it's their highest priority, but I presume it will get fixed sooner or later, especially as they're not the only ones with this bug. But knowing the people at g++, it will get fixed sooner or later---they take conformance seriously. And other compilers reject the code now. The fact remains that you posted code which was illegal, and only compiles because of a compiler bug, and which doesn't work correctly when it does compile. Both of your errors are frequent ones, of course, but a competent programmer would learn from them, instead of trying to defend something which is blatently wrong. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#12 |
|
Messages: n/a
Hébergeur: |
On Oct 16, 8:35 pm, "Chris Thomasson" <cris...@comcast.net> wrote:
> "James Kanze" <james.ka...@gmail.com> wrote in message > news:1192521490.065199.127910@v29g2000prd.googlegr oups.com... > On Oct 15, 3:58 pm, "Laurent D.A.M. MENTEN" > <laurent.men...@teledisnet.be> wrote: > >> > You're right about the race condition, of course, and I doubt > >> > that his code would work on a modern system (dual core or more). > >> > But it won't even compile with a conformant C++ compiler. His > >> > function glue must be `extern "C"', and thus cannot be a member. > >> > (On the other hand, it *can* be static, in order to avoid all > >> > risk of name collision.) > >> Anyway, it does compile (gcc 3.3.5 + linux 2.6.11 + x86_64) > >This is a known bug in g++, which will presumably be fixed in a > >future release. At which point, your code will stop compiling. > > [...] > >> It is a shame so many people here are not able to understand not > >> everyone in this world is a C++ guru or theorician. > >It's a shame so many people don't care about writing correct > >code. > >[...] > Not sure why C++ threading has to be overally complicated... I > mean, why not something simple like: > http://appcore.home.comcast.net/misc...hread_cpp.html > ??? It's not the threading per se which is complicated, it's the management of the other associated resources. Both Gianni's code (if I've understood it correctly) and Boost support the simple use of detached threads, where the user doesn't have to worry about the lifetime of the thread object. In other words, the creator of the thread doesn't have to maintain an object himself, and delete it when the thread is ended. IMHO, that's probably overkill (and at least in the case of Boost, it works against you) if you're joining, and want to recover work done by the thread. On the other hand, it's very, very convenient for detached threads---in the implementation you posted, if you don't join, you leak resources, and if the thread is of the fire and forget nature, you have to do a lot of extra work to ensure destructing your thread object correctly when the thread has terminated. One of the real problems with threading is that so many people consider only a single solution, where as it's definitly not a case of one size fits all. I use a simple thread class, very similar to the one which started the discussion here (but using the strategy pattern, of course, and an `extern "C"' function to start the thread) for joinable threads; my interface for detached threads is a simple function, much like yours, but I copy the functional object, and interlock with the new thread (like Gianni and Boost) for detached threads. And I'm sure I've not begun to cover all of the various uses. -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
![]() |
| Outils de la discussion | |
|
|