2012-01-11 8 views
1

ここではC++イベントを実装しようとしています。このイベントの実装を同期する最良の方法は

class Event{ 
    typedef std::tr1::function<void(int&)> CallbackFunction; 
    std::list<CallbackFunction> m_handlers; 

    template<class M> 
    void AddHandler(M& thisPtr, void typename (M::*callback)(int&)) 
    {   
     CallbackFunction bound = std::tr1::bind(callback, &thisPtr, _1); 
     m_handlers.push_back(bound); 
    } 

    void operator()(int& eventArg) 
    { 
     iterate over list... 
     (*iter)(eventArg); 

    }} 

ここでのトラブルはスレッドセーフです。 AddHandleroperator()が同時に呼び出されると、事態が壊れる可能性があります。

これを同期するにはどのような方法が最適ですか?ミューテックスを使用するとパフォーマンスが低下する可能性があります。この場合、boost :: signalsやC#eventのシーンで何が起こるのだろうか。

+1

1秒あたりの頻度はどれくらいですか?この同じオブジェクトをすべてのスレッドで使用できるようにする必要があるのはなぜですか?彼らはすべて、1つのスレッドで受信したmutexメッセージを送信できませんか? –

答えて

1

mutexは間違いなくあなたが探しているものです。各イベントに独自のミューテックスがある場合は、パフォーマンスについてあまり心配する必要はありません。その理由は、イベントを処理している間に多くのハンドラを追加しない限り、ミューテックスが競合することはまずありません。

しかし、同じオブジェクトに対してoperator()メソッドを呼び出す複数のスレッドがある場合、このmutex となる可能性があります。しかし、それがなければ、どうやってスレッドセーフな方法でコールバックを確実に呼び出すことができますか? (整数参照を渡してvoidを返すことに気がつくので、これらは再入可能ハンドラではないと仮定しています)

EDIT:あなたのコメントで非常に良い質問。正直言って、私は決してミューテックスが同期的な方法で使用されるときにオーバーヘッドが多くあったかどうかにはほとんど考えなかった。だから私はこの小さなテストをまとめました。

 

#include <stdio.h> 
#include <pthread.h> 

#define USE_PTHREAD_MUTEX 1 

int main(int argc, char * argv[]) { 

pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL); 

long useless_number = 0; 
long counter; 

    for(counter = 0; counter < 100000000; counter++) { 
    #if USE_PTHREAD_MUTEX 
    pthread_mutex_lock(&mutex); 
    #endif 
    useless_number += rand(); 

    #if USE_PTHREAD_MUTEX 
    pthread_mutex_unlock(&mutex); 
    #endif 
    } 

    printf("%ld\n", useless_number); 

} 
 

私のシステムでこれを実行し、以下のランタイムを取得しました。

USE_PTHREAD_MUTEX 0の場合、平均ランタイムは1.2秒です。

USE_PTHREAD_MUTEX 1では、平均実行時間は2.8秒です。

あなたの質問に答えるために、間違いなくオーバーヘッドがあります。あなたのマイレージは異なる場合があります。さらに、リソースへのアクセスのために複数のスレッドが競合している場合、必然的にブロックするために多くの時間が費やされます。また、純粋に同期的なコンテキストでは、mutexのロック/ロック解除を待つよりも、共有リソースへのアクセスに費やす時間が長くなる可能性があります。言い換えれば、ミューテックスロジック自体のオーバーヘッドは、おそらくこれらのものと比較して重要ではないでしょう。

+0

競合のないミューテックスはどれくらいの費用がかかりますか?私は「AddHandler」を100000回も同期して呼び出すと言う。ここで予想されるパフォーマンスの低下は何ですか? – Leo

+0

大きな質問です。私は少しのテストで私の答えを更新しました。そのようなパフォーマンスへの影響についても私は好奇心が強いからです。 – Tom

+2

operator()でコールバックリストのコピーを作成します。ロックの持続時間の間、コピーの間だけミューテックスを取得します。また、デッドロックの危険があるため、通知ループの周りにミューテックスを必要としません。スレッドの競合が低すぎる - ミューテックスより4倍高速ですが、ロックが高競合で長すぎる場合(ロックされている間のメモリ割り当てを避けるなど)、災害の場合はスピンロックの使用を検討できます。プロファイリングは確かに知る唯一の方法です。 – mcmcc

2

まず、実装の可能性が不十分であると判断する前に、実際にどのようなパフォーマンス要件が必要かを判断する必要があります。これらのイベントを毎秒何千回も引き起こしていますか?あなたがいれば、実際にハンドラコンテナにハンドラを追加する必要が本当にありますか?

両方の質問に対する回答が何らかの理由で実際に「はい」の場合、ロックフリーのコンテナを調査する必要があります。これは、stlリストを使用できるのではなく、独自のコンテナを構築することを意味します。ロックフリーのコンテナは、リストの末尾がNULLかそれ以外の場合に原子的に決定するために、原子組み込み関数(たとえばWindowsのInterlockedCompareExchangeなど)を使用します。彼らは実際に同様の組み込み関数を使って実際にリストに追加します。複数のスレッドがハンドラを同時に追加しようとすると、さらに複雑になります。

しかし、マルチコアマシンと命令の再順序付けなどの世界では、これらのアプローチは危険に満ちている可能性があります。私は個人的に、あなたが記述したものと似ていないイベントシステムを使用しています。クリティカルセクション(これは少なくともウィンドウでは効率的です)で使用し、パフォーマンスの問題は発生しません。しかし、一方では、イベントシステムを介して送信されるものは約20Hz以上のものはありません。

パフォーマンス関連の質問と同様に、回答は常に別の質問に対する回答に基づいています。正確にどこであなたのパフォーマンスが必要ですか?

+0

このイベントはジェネリックヘルパーなので、使用パターンを効率的に処理する場合は最適です。あなたの経験を共有してくれてありがとう、これは私をcritical_sectionソリューションに押し込む。 – Leo

1

リストが本当にあなたのクラスなら、その性質上、アクセスするたびにロックする必要はありません。ミューテックスをロックしてリストの最後に投稿し、最後に到達したと思うときはロックします。

クラス内のハンドラの数をカウントする必要があります。反復処理を開始しようとしているときに、この数値に達するまで、ロックせずに喜んで反復することができます。

ハンドラを削除しようとすると、スレッド競合の問題が増えます。

+0

これは大きな展望です。私は連動して...それを数えるために使うことができます。しかし残念なことに、残念ながら私はおそらく "削除"機能を追加し、より良い解決策が必要になります。何かご意見は? – Leo

+0

「退会」を受けた場合は、リストから移動するのではなく、フラグを付けてレコードをマークします。イベントハンドラスレッドはフラグをチェックして、リストから未登録のアイテムを削除できます。リストの最後にのみ競合があるため、最後のリストアイテムの場合はロックする必要があります。 – CashCow

関連する問題