同期に関する注意。 デッドロック



過酷な時期に、プロセッサのパワーの増加(周波数)が停止し、幅(スレッドの数)が増加し始めたとき、同期の問題は非常に深刻です。 実際にこの問題に直面したとき、私はこのタスクが一見すると思われるよりもはるかに複雑であり、どれだけのレーキが隠れているかを自分で感じました。 この問題に取り組む過程で、私はhabrasocietyに紹介したいいくつかの興味深いパターンがありました。





マルチスレッドをプログラミングする際に対処しなければならない最も厄介な問題の1つは、デッドロックです。 ほとんどの場合、スレッドが既にリソースAをブロックし、その後Bをブロックしようとし、別のスレッドがリソースBをブロックし、その後リソースAをブロックしようとする場合に発生します。 、テスト中にキャッチすることはほとんど不可能です。 したがって、このバグの可能性さえも防ぐ必要があります! どうやって? とても簡単です。 ここで私がSynchronizationManagerと呼んだパターン、すなわち 同期マネージャー。 このパターンは、十分に多数のさまざまなリソースの同期を提供する必要がある場合を対象としています。



まず、共有する必要があるすべてのリソースを特別な基本クラスから継承する必要があります。

class SynchPrim { enum{ NOT_LOCKED=0 }; public: SynchPrim(): lockId_( NOT_LOCKED ){} SynchPrim( const SynchPrim& ): lockId_( NOT_LOCKED ){} SynchPrim operator =( const SynchPrim& ){} private: template< typename T >friend class SynchronizationManager; int getLockId()const{ return lockId_; } int lockId_; };
      
      







まあ、これまでのところ、すべてが簡単です。 しかし、クラスに先祖を追加できない、または追加したくない場合はどうなりますか? 大丈夫です、あなたはそれのためにラッパーを書くだけです:

 template< typename T > class SynchPrimWrapper : public T, public SynchPrim{};
      
      





そして、オブジェクトを宣言するときに使用します:

 SynchPrimWrapper< Resource1 > resource1;
      
      







このパターンの主なアイデアは、現時点で必要となる可能性のあるすべての共有リソースを同時にブロックし、同時に解放する必要があるため、デッドロックが発生しないことです。 以前のリソースを解放せずに新しいリソースをブロックしようとすると、assertマクロが機能します。



まず、作業するリソースのリストを作成します。 C配列またはstd ::ベクトルのいずれかです。

 SynchPrim* lock_list[] = { &resource1, &resource2, &resource3 };
      
      





次に、1回の呼び出しですべてをブロックします。

 SynchronizationManager::getInstance().lock( lock_list+0, lock_list+3 );
      
      





ところで、ロック機能は非同期ロックをサポートしています。 占有リソースが解放されるまでスレッドを待機させたくない場合は、最後のパラメーターとしてfalseを渡すだけです。

リソースが不要になったら、次を呼び出してリソースを解放します。

 SynchronizationManager::getInstance().unlock( lock_list+0, lock_list+3 );
      
      







まあ、実際にはクラス:

 template< class TSynchronizator >//      lock, unlock, wait, signal   ThreadType class SynchronizationManager : public Singelton< SynchronizationManager< TSynchronizator > > { public: /*         needWait -    true -   ,       false,    */ template< typename Iterator > bool lock( const Iterator& begin, const Iterator& end, bool needWait= true ); /*          */ template< typename Iterator > void unlock( const Iterator& begin, const Iterator& end ); private: TSynchronizator synh_; #ifdef _DEBUG typedef typename TSynchronizator::ThreadType ThreadId; typedef std::set< ThreadId > LockedThreads; LockedThreads lthread_; #endif };
      
      







同期戦略は、あらゆる手段を使用して、事実上すべてのプラットフォームで作成できます。 通常、これはミューテックスと条件です。



実装:



 template< class TSynchronizator > template< typename Iterator > bool SynchronizationManager< TSynchronizator >::lock( const Iterator& begin, const Iterator& end, bool needWait ) { synh_.lock(); ThreadId threadId = synh_.getThreadId(); bool isFree = false; #ifdef _DEBUG assert( lthread_.find( threadId ) == lthread_.end() ); #endif while( !( isFree = std::find_if( begin, end, std::mem_fun( &SynchPrim::getLockId ) ) == end ) && needWait ) synh_.wait(); if( isFree ) for( Iterator it = begin; it != end; it++ ) (*it)->lockId_ = threadId; #ifdef _DEBUG if( isFree ) lthread_.insert( threadId ); #endif synh_.unlock(); return isFree; } template< class TSynchronizator > template< typename Iterator > void SynchronizationManager< TSynchronizator >::unlock( const Iterator& begin, const Iterator& end ) { synh_.lock(); #ifdef _DEBUG ThreadId threadId = synh_.getThreadId(); assert( lthread_.find( threadId ) != lthread_.end() ); lthread_.erase( threadId ); #endif for( Iterator it = begin; it != end; it++ ) (*it)->lockId_ = SynchPrim::NOT_LOCKED; synh_.signal(); synh_.unlock(); }
      
      





当然、このアプローチには欠点がないわけではありません。 必要なリソースを事前に知ることは常に可能とは限りません。 この状況では、すでに割り当てられているリソースのブロックを解除し、新しいリソースでブロックする必要がありますが、これはオーバーヘッドであり、潜在的なデッドロックを取り除く価値があります。 さらに、現在の形式では、パターンはロック解除されたリソースへのアクセスを制御することを許可せず、リソースの使用後のロック解除を保証しません。 これをどのように行うかについては、次の記事で説明します。



All Articles