2009-08-14 22 views
30

私はC++ API(LinuxとSolaris用)をスレッドセーフなものにしようとしています。そのため、内部データ構造を破壊することなくその関数を別のスレッドから呼び出すことができます。私の現在のアプローチでは、メンバ変数へのすべてのアクセスを保護するためにpthread mutexを使用しています。これは単純なゲッター関数がmutexのロックとアンロックを行うことを意味します。特に、APIが主にシングルスレッドのアプリケーションで使用されるため、オーバーヘッドが心配です。pthread mutexのオーバーヘッド?

  • あなたがそうでないものに対してロックを使用、シングルスレッドのアプリケーションの性能を持つ任意の経験を持っています:

    だから、私はお願いしたいのですが?

  • これらのロック/ロック解除コールは、たとえば、 boolメンバ変数の単純な "return this-> isActive"アクセス?
  • あなたはそのような可変アクセスを保護するためのより良い方法を知っていますか?
+1

おかげで、多くの答えのためにたくさん!いくつかの説明: - 現在のAPIには、OpenGLと同様の制限があります。オブジェクトは、作成された同じスレッドからのみ使用/操作できます。私はその制限を取り除きたい。 - "Accessor"メソッドについての良い点 - 今後のAPIでこれを念頭に置いておきますが、現行のAPIをそのように変更することはできません。 - パブリックメソッドはわずかです。そこで、私はこれらすべてのメソッドにロックを追加するつもりです(ほとんどがかなり高いレベルです) - はい、私はマルチスレッド化に新しいです:-) –

答えて

2

mutexには、OSコンテキストスイッチが必要です。それはかなり高価です。 CPUはそれでも何十万回も1秒間に何回も問題を起こすことはありませんが、よりもずっと高価です。にミューテックスがあります。 にすると、すべてと変わる可能性があります。

これはおそらくあなたが望むものではありません。この種のブルートフォースロッキングは、デッドロックを招く傾向があります。

あなたはこのような可変アクセスを保護するためのより良い方法を知っていますか?

可能な限り小さなデータを共有するようにアプリケーションを設計します。コードのいくつかのセクションは、おそらくミューテックスで同期されるべきですが、実際に必要なものだけが同期されるべきです。そして通常は個の個別のの可変アクセスがありますが、可変アクセスのグループを含むタスクはアトミックに実行する必要があります。 (おそらく、あなたはis_activeフラグをいくつかの他の変更と共にセットする必要があります。そのフラグをセットしてオブジェクトにそれ以上の変更を加えるのは意味がありますか?)

+1

ええ、それは良い点です、露出していないデータをロックする必要はありません。私の戦略はほとんど常にです: 1.すべてのデータはプライベートです 2.すべてのパブリック/保護されたメソッド で途中でロックされていません。3.ロックは、スタックにあるオブジェクトで、キャッチされていない例外がデータをロック解除します。 –

+0

*すべての公開/保護されたメソッドをロックすると言っていますか?それは愚かです。他のスレッドから呼び出すことは意味があります。 – jalf

+3

マルチスレッドライブラリで正しい動作をさせる唯一の方法は、ライブラリのユーザーが正しく使用することを期待することです。人々が悪用することができるすべてのスパムロックは、問題を解決したり正確性を保証しません。ユーザーに、他のスレッドから何が呼び出されているか、何が呼び出されていないかを知らせて、それらのメソッドをロックするだけです。 – jalf

4

私は同様のライブラリを行いました。ロックパフォーマンス。 (私はそれらがどのように実装されているかを正確に伝えることはできないので、大したことではないという結論を出すことはできません)

私はまずそれを正しくパフォーマンス。私はよりよい方法を知らない。それがミューテックスのために作られたものです。

シングルスレッドクライアントの代わりに、プリプロセッサを使用して、ライブラリのロックされていないバージョンとロックされていないバージョンを構築することができます。たとえば、シングルスレッド版とマルチスレッド版の両方を配布する場合は、維持するための追加ビルドが追加されます。

+1

ロックを使用しても、それはどこにでもデッドロックを引き起こす可能性があります。 – jalf

+1

問題のライブラリがユーザーにコールバックせず、何もロックしていない場合、デッドロックは発生しません。 – caf

+1

あなたのライブラリのスレッド版とスレッド版以外のバージョンを強くお勧めします。スレッド自体を使用しないアプリケーションやライブラリの作者は、非スレッドバージョンをリンクしたいと思うので、スレッドバージョンを使用している別のライブラリへの依存を取りやめると、すべての地獄が緩む可能性があります。 –

3

私はWindowsから、ミューテックスはカーネルオブジェクトであることを伝えることができます。そのため、(かなり)重要なロックオーバーヘッドが発生します。より良い実行ロックを得るには、必要なのはスレッドで動作するものだけがクリティカルセクションを使用することです。これは、単一のプロセス内のスレッドだけでは、プロセス間では機能しません。

しかし、Linuxは、マルチプロセスロッキングとは全く違った獣です。私はミューテックスが原子CPU命令を使用して実装され、プロセスにのみ適用されることを知っています。したがって、Win32のクリティカルセクションと同じパフォーマンス、つまり非常に高速です。

もちろん、最も速いロックは、まったく持っていないか、またはそれらをできるだけ使用しないことです(しかし、あなたのライブラリーが大量のスレッド環境で使用される場合、短いaロックする、何かをする、ロックを解除する、他の何かをする、そして再びロックすることは、タスク全体にわたってロックを保持するよりも優れている - ロックのコストはロックにかかる時間ではなく、スレッドが周りにいる時間)他のスレッドが望んでいるロックを解放するのを待っている間に親指をつまずく!)

1

メンバ変数アクセスの場合は、読み書きロックを使用する必要があります。

多くの場合、あなたのコンパイラが(gccやicc __sync_fetch *()などを使っている場合)アトミックな組み込み関数を使うことができますが、正しく処理することは難しくありません。

アクセスがアトミックであることが保証できる場合(たとえば、x86ではdwordの読み取りまたは書き込みはアトミックで、アライメントされている場合はリード - モディファイ - ライトではありません)、しばしばロックを回避して使用できます代わりに揮発性ですが、これは移植性がなく、ハードウェアに関する知識が必要です。

+1

プロセッサの助けなしに、__sync_fetch *というマルチコアシステムでは、読み取りや書き込みがアトミックであることを保証することはできません。単一のコアマシンでは、マシンワードの読み書きがアトミックであると仮定することは安全ですが、シングルコアはドドーの道を進んでいます。 –

+1

@Caspin、_reading_ dwordは、別のコアが同じメモリ位置に書き込んでいるにもかかわらず、いくつかの "古い"ビットといくつかの "新しい"ビットを混ぜ合わせることは決してないという意味で、同じ時間です(ただし、同じ意味でqwordはx86では_not_ atomicではありません)。私はあなたが "原子"と "秩序"を混同していると思います。 –

+1

@キャスピン、あなたは間違っています。インテル®64アーキテクチャー・メモリーからの引用ホワイトペーパー: "インテル®64メモリーの注文により、以下のメモリー・アクセス 命令ごとに、メモリータイプにかかわらず構成メモリー操作が単一メモリーアクセス として実行されているように見えます。 。 .. 3.アドレスが4 バイト境界に揃ったダブルワード(4バイト)を読み書きする命令 4. 8 バイトにアドレスが整列したクワッドワード(8バイト)を読み書きする命令境界。" 他の多くの中では、これは原子です。ところで、インテル64はIA-32と64の両方を指しています。 – hirschhornsalz

7

これは少し話題ですが、あなたはスレッディングの初心者のようです。一つは、スレッドが重なる場所をロックするだけです。次に、それらの場所を最小化しようとします。また、すべてのメソッドをロックしようとするのではなく、スレッドがオブジェクト全体で何をしているのかを考え、それを単一の呼び出しとしてロックします。ロックを可能な限り高くするようにしてください(これにより、効率が上がり、デッドロックを避けることができます)。しかし、ロックは「作成」しないので、スレッドがどこに重なっているかによって精神的にはコードをクロスオーガナイズする必要があります。

+1

問題の根幹を逸した素晴らしい仕事。複数のスレッドに触れるときに可能な限りすべてをカプセル化します。アクセサはカプセル化を弱める。 –

36

すべての現代のスレッド実装では、ユーザスペース内で(マシン命令のほんの数個で)完全なミューテックスロックを処理できます。競合が発生した場合のみ、ライブラリはカーネルを呼び出す必要があります。

もう1つの留意すべき点は、アプリケーションがpthreadライブラリに明示的にリンクしていない場合(単一スレッドアプリケーションなので)、ダミーのpthread関数(まったくロックしない) - アプリケーションがマルチスレッド(およびpthreadライブラリへのリンク)の場合にのみ、完全なpthread関数が使用されます。

最後に、すでに指摘したように、getterメソッドをmutexを使ってisActiveのように保護する必要はありません。呼び出し元が戻り値を見る機会を得たら、その値はすでに変更されました(ミューテックスはゲッターメソッド内でのみロックされるため)。

+1

+1 for para .. –

+19

最後の段落は真ではありません。ゲッターでミューテックスを使用しないと、呼び出し元が無効な値を取得することがあります.2つ目のスレッドは、ソース値_partly_を上書きしてコピー。 – nob

+7

移植性は100%ではありませんが、通常のプリミティブ(int、bool、またはfloat)は、ほとんどのプラットフォームで保証されているため、アトミックに読み込むことができます。私はglibcのマニュアルでバックアップされています。実際には、intやint以外の整数型はアトミックであると仮定することができますが、ポインタ型はアトミックであると仮定することもできます。 GNU Cライブラリがサポートしているすべてのマシンと、われわれが知っているすべてのPOSIXシステムで使える」 http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_21.html#SEC360 – Gabe

0

まあ最適ではありませんが、単純なアプローチは、mutexのロックとアンロックの周りにマクロを配置することです。その後、コンパイラ/ makefileオプションを使用してスレッドを有効/無効にします。

Ex。

コンパイル時にgcc -DTHREAD_ENABLEDを実行するとスレッドが有効になります。

大規模なプロジェクトでもこのメソッドを使用しません。しかし、あなたが非常に単純なものを望む場合にのみ。

+1

私はこの答えを他の人に話したのと同じ理由でこれをお勧めしません。また、 'pthread.h'をインクルードした後に' #define pthread_mutex_lock'を使用すると、未定義の動作が発生します( 'pthread.h'を可能)。 –

2

私は、pthred_mutex_lock/unlockを使用する費用について不思議でした。 私は、 ミューテックスを使用せずに、1500-65Kバイトのどこかにコピーするか、ミューテックスを使用して、必要なデータへのポインタを1回だけ書き込む必要があるシナリオを持っていました。

私は私が4000の未満かそこらのバイトをコピーした場合は、各

gettimeofday(&starttime, NULL) 
COPY DATA 
gettimeofday(&endtime, NULL) 
timersub(&endtime, &starttime, &timediff) 
print out timediff data 

または

ettimeofday(&starttime, NULL) 
pthread_mutex_lock(&mutex); 
gettimeofday(&endtime, NULL) 
pthread_mutex_unlock(&mutex); 
timersub(&endtime, &starttime, &timediff) 
print out timediff data 

をテストするために短いループを書いた、そしてまっすぐコピー操作が少ない時間がかかりました。しかし、4000バイトを超えるコピーを行っていた場合は、ミューテックスのロック/アンロックを行うのに費用がかかりませんでした。ミューテックスのロック/アンロックに

タイミングが長いミューテックスがOSのコンテキストスイッチを必要とする「は、約2マイクロ秒

+0

このテストにはどのオペレーティングシステムとバージョン、どのプロセッサを使用していましたか?システムのクロックの細かさはどのようなものでしたか(壁/ CPU /スレッド)、どのくらい頻繁にオペランドを繰り返しましたか?あなたはまったくやり直しましたか、またはサインイン時間を取ったのですか? Linux 3.2 x64では、私のマシンの単一のコアが最大で〜4500万の 'pthread_mutex_lock()'/'_unlcok()'のペアを毎秒実行することができるからです。それはまさにハイエンドシステムではありません。しかし今回は明らかにキャッシングとユーザー空間処理のメリットがあります。 –

20

かかっCURRENTTIMEため gettimeofdayのための時間を含む3と5マイクロ秒の間で走った。それはかなりあります

  • これは、mutexがfutex'esと呼ばれるものを使って実装されているLinuxでは当てはまりません。競合のない(すなわち、まだロックされていない)ミューテックスを取得することは、いくつかの簡単な命令の問題を指摘するものであり、典型的には現在のハードウェアと共に25ナノ秒の領域にある。詳細情報については

Futex

Numbers everybody should know

関連する問題