web-dev-qa-db-ja.com

pthread mutexのオーバーヘッド?

C++ API(LinuxおよびSolaris用)をスレッドセーフにして、内部のデータ構造を壊すことなく、その関数を異なるスレッドから呼び出せるようにしています。現在のアプローチでは、pthread mutexを使用して、メンバー変数へのすべてのアクセスを保護しています。これは、単純なゲッター関数がミューテックスをロックおよびロック解除することを意味します。特に、ミューテックスロックが純粋なオーバーヘッドのように見えるシングルスレッドアプリでAPIがほとんど使用されるため、このオーバーヘッドが心配です。

だから、私は尋ねたいと思います:

  • ロックを使用するシングルスレッドアプリと使用しないシングルスレッドアプリのパフォーマンスの経験はありますか?
  • これらのロック/ロック解除の呼び出しは、たとえばと比べてどれだけ高価か。 boolメンバー変数に対する単純な「return this-> isActive」アクセス?
  • そのような可変アクセスを保護するより良い方法を知っていますか?
31
oliver

すべての最新のスレッド実装は、競合しないmutexロックをユーザー空間で完全に処理できます(いくつかのマシン命令を使用)-競合がある場合のみ、ライブラリはカーネルを呼び出す必要があります。

考慮すべきもう1つの点は、アプリケーションがpthreadライブラリに明示的にリンクしない場合(シングルスレッドアプリケーションであるため)、ダミーのpthread関数(ロックをまったく行わない)しか取得できないことです。アプリケーションはマルチスレッド(およびpthreadライブラリへのリンク)であり、完全なpthread関数が使用されます。

そして最後に、他の人がすでに指摘したように、ミューテックスを使用してisActiveなどのゲッターメソッドを保護しても意味がありません-呼び出し元が戻り値を見る機会を得たら、値はすでに変更されている可能性があります( mutexはゲッターメソッド内でのみロックされます)。

36
cmeerw

「ミューテックスにはOSコンテキストスイッチが必要です。それはかなり高価です。」

  • これは、ミューテックスがfutexと呼ばれるものを使用して実装されているLinuxでは当てはまりません。競合しない(つまり、まだロックされていない)ミューテックスを取得することは、cmeerwが指摘するように、いくつかの簡単な指示の問題であり、通常、現在のハードウェアで25ナノ秒の領域です。

詳細: Futex

誰もが知っておくべき数字

20
BillT

これは少し話題から外れていますが、あなたはスレッドに慣れていないようです-一つには、スレッドがオーバーラップできる場所でのみロックします。次に、それらの場所を最小化してみます。また、すべてのメソッドをロックしようとするのではなく、オブジェクトでスレッドが(全体的に)何をしているかを考え、それを1回の呼び出しでロックします。ロックを可能な限り高くするようにしてください(これにより効率が向上し、デッドロックを回避するために/ help /することができます)。しかし、ロックは「構成」しないので、少なくともスレッドがどこにあり、オーバーラップするかによって、コードを精神的に少なくとも組織的に編成する必要があります。

7
JDonner

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

私は最初にそれを正しくするために行き(つまりロックを使う)、そしてパフォーマンスについて心配します。私はもっ​​と良い方法を知りません。それがミューテックスの目的です。

シングルスレッドクライアントの代替方法は、プリプロセッサを使用して、ライブラリの非ロックバージョンとロックバージョンをビルドすることです。例えば。:

#ifdef BUILD_SINGLE_THREAD
    inline void lock () {}
    inline void unlock () {}
#else
    inline void lock () { doSomethingReal(); }
    inline void unlock () { doSomethingElseReal(); }
#endif

もちろん、シングルスレッドとマルチスレッドの両方のバージョンを配布するので、維持するビルドが追加されます。

4
Peter Cardona

Windowsから、mutexはカーネルオブジェクトであるため、(比較的)かなりのロックオーバーヘッドが発生することがわかります。パフォーマンスの高いロックを取得するには、スレッドで機能するロックが必要な場合は、クリティカルセクションを使用するだけです。これはプロセス全体では機能せず、単一のプロセス内のスレッドのみで機能します。

ただし、Linuxはマルチプロセスロックとはまったく異なる獣です。 mutexはアトミックCPU命令を使用して実装され、プロセスにのみ適用されることを知っています。そのため、それらはwin32クリティカルセクションと同じパフォーマンス、つまり非常に高速です。

もちろん、最速のロックは、何も持たないか、それらをできるだけ少なくすることではありません(ただし、libがスレッドの多い環境で使用される場合は、できるだけ短い時間ロックする必要があります。ロック、何かをする、ロックを解除する、何か他のことをする、そしてもう一度ロックする方が、タスク全体でロックを保持するよりも良い-ロックのコストは、ロックにかかる時間ではなく、スレッドが親指をいじるのを待つ時間である別のスレッドが必要なロックを解放するために!)

3
gbjbaanb

pthred_mutex_lock/unlockを使用する費用に興味がありました。ミューテックスを使用せずに1500〜65Kバイトの任意の場所にコピーするか、ミューテックスを使用して必要なデータへのポインターを1回書き込む必要があるシナリオがありました。

それぞれをテストするための短いループを作成しました

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バイト以下の場合、ストレートコピー操作にかかる時間が短縮されました。ただし、4000バイトを超えるコピーを行っている場合は、ミューテックスのロック/ロック解除を行う方がコストが低くなります。

ミューテックスロック/ロック解除のタイミングは、現在の時間のgettimeofdayの時間を含めて、3から5 usecの間で実行されました。

2
user413894

ミューテックスにはOSコンテキストスイッチが必要です。それはかなり高価です。 CPUは、1秒あたり数十万回も問題なく実行できますが、ミューテックスをnotよりもはるかに高価です。 every変数へのアクセスを許可するのはおそらくやり過ぎです。

また、おそらくあなたが望むものではありません。このようなブルー​​トフォースロックは、デッドロックにつながる傾向があります。

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

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

2
jalf

メンバー変数アクセスの場合は、読み取り/書き込みロックを使用する必要があります。これにより、オーバーヘッドがわずかに減少し、ブロックせずに複数の同時読み取りが可能になります。

コンパイラーが提供している場合(gccまたはicc __sync_fetch *()などを使用している場合)、多くの場合、アトミックビルトインを使用できますが、正しく処理することは非常に困難です。

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

1
Gunther Piez

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

例.

#ifdef THREAD_ENABLED
#define pthread_mutex_lock(x) ... //actual mutex call
#endif

#ifndef THREAD_ENABLED
#define pthread_mutex_lock(x) ... //do nothing
#endif

次に、コンパイル時にgcc -DTHREAD_ENABLEDスレッドを有効にします。

繰り返しますが、大規模なプロジェクトではこの方法を使用しません。ただし、かなり単純なものが必要な場合に限ります。

0
mox1