1. ホーム
  2. synchronization

[解決済み】ミューテックスではなく、スピンロックを使うべき時とは?

2022-03-25 18:09:57

質問

どちらも同じ仕事をしていると思うのですが、同期を取るためにどちらを使うかはどのように決めるのですか?

どのように解決するのですか?

理論編

理論的には、あるスレッドがミューテックスをロックしようとして成功しなかった場合、ミューテックスがすでにロックされているため、そのスレッドはスリープ状態になり、すぐに他のスレッドが実行できるようになります。そして、そのスレッドが起動するまでスリープ状態を継続します。これは、その前にロックを保持していたスレッドによってミューテックスのロックが解除された場合に起こります。あるスレッドがスピンロックをロックしようとして失敗した場合、最終的にロックに成功するまで再試行し続けますので、他のスレッドがその場所を取ることはありません(ただし、現在のスレッドのCPU実行時間量子値を超えた場合、OSは強制的に他のスレッドに切り替えます)。

問題点

ミューテックスの問題は、スレッドをスリープさせたり、再び起動させたりするのは、どちらもかなり高価な操作で、かなりのCPU命令を必要とし、したがって時間もかかるということです。もし、ミューテックスが非常に短い時間だけロックされていた場合、スレッドをスリープさせ、再び起動させるのにかかる時間は、スレッドが実際にスリープした時間をはるかに超え、スピンロックで常にポーリングすることによってスレッドが無駄にした時間さえも超えるかもしれません。一方、スピンロックへのポーリングは常にCPU時間を浪費し、ロックが長い時間保持されると、より多くのCPU時間を浪費することになり、代わりにスレッドがスリープしていた方がずっとよかったと考えられます。

解決策

スピンロックのポーリングが唯一の利用可能なCPUコアをブロックしている限り、他のスレッドは実行できず、他のスレッドが実行できないため、ロックも解除されないからです。つまり、スピンロックはそのようなシステムではCPU時間だけを浪費し、実質的な利益はないのです。もし、そのスレッドがスリープしていたら、他のスレッドが一度に実行され、ロックを解除して、最初のスレッドが再び起動したときに処理を続けることができたかもしれません。

マルチコア/マルチCPUシステムでは、非常に短い時間だけ保持されるロックがたくさんあるため、スレッドをスリープさせて再び起動させるという無駄な時間が発生し、実行時のパフォーマンスが著しく低下してしまう可能性があるのです。代わりにスピンロックを使用すると、スレッドは実行時量子をフルに活用できるようになり(常に非常に短い時間だけブロックし、その後すぐに作業を続行する)、処理スループットが大幅に向上します。

実践編

プログラマーは、(ターゲットアーキテクチャのCPUコア数が不明なため)ミューテックスとスピンロックのどちらが良いかを事前に知ることができない場合が非常に多く、また、オペレーティングシステムは、あるコードがシングルコア環境とマルチコア環境のどちらに最適化されているかを知ることができないため、ほとんどのシステムではミューテックスとスピンロックを厳密に区別していません。実際、最近のオペレーティングシステムは、ハイブリッドミュテックスとハイブリッドスピンロックを採用しています。実際にはどうなのでしょうか?

ハイブリッドミュテックスは、マルチコアシステムでは、最初はスピンロックと同じように振る舞います。スレッドがミューテックスをロックできない場合、すぐにスリープさせることはできません。ミューテックスはすぐにロック解除される可能性があるからです。一定時間(または再試行など)経過してもロックが解除されない場合のみ、そのスレッドは本当にスリープ状態になります。同じコードがシングルコアしかないシステムで実行される場合、ミューテックスはスピンロックしませんが、上記のように、それは有益なことではありません。

ハイブリッド・スピンロックは、最初は通常のスピンロックと同じように動作しますが、CPU時間を無駄にし過ぎないように、バックオフ戦略を持つことがあります。通常スレッドをスリープさせませんが(スピンロック使用時にスリープさせたくないため)、スレッドを停止させ(即時または一定時間後、これをquot;yielding;といいます)、別のスレッドを実行させ、スピンロックが解除される機会を増やします(まだスレッド切り替えのコストはありますが、スレッドをスリープさせて再び起動させるコストはかかりません)。

概要

もし迷ったら、ミューテックスを使いましょう。通常、ミューテックスの方が良い選択ですし、最近のシステムでは、有益と思われる場合には、非常に短い時間だけスピンロックすることができます。スピンロックを使うことでパフォーマンスが向上することもありますが、それは特定の条件下でのことであり、あなたが迷っているということは、現在スピンロックが有益と思われるプロジェクトに携わっていないことを物語っています。スピンロックまたはミューテックスを内部で使用できる独自のロックオブジェクト(例えば、そのようなオブジェクトを作成するときにこの動作を設定できる)を使用することを検討することができます。

アップデート:iOSに関する警告

もしあなたのシステムがスレッドスケジューラーを持っていて、その優先順位がどんなに低くても、どのスレッドも最終的に実行する機会を得ることが保証されていない場合、スピンロックは永久的なデッドロックにつながる可能性があります。iOSのスケジューラは、スレッドのクラスを区別し、下位クラスのスレッドは、上位クラスのスレッドが実行を希望しない場合にのみ実行されます。このため、上位クラスのスレッドが常に利用可能である場合、下位クラスのスレッドには CPU 時間が与えられず、何らかの作業を実行する機会もありません。

問題は次のようになります。あなたのコードは低プリオクラスのスレッドでスピンロックを取得し、そのロック中に時間量子を超えてしまい、スレッドの実行を停止してしまいます。このスピンロックを再び解放する唯一の方法は、そのlow prioクラスのスレッドが再びCPU時間を得た場合ですが、これが確実に起こるとは限りません。常に実行したい高プリオクラスのスレッドがいくつかあり、タスクスケジューラは常にそれらを優先させます。そのうちの1つはスピンロックに出くわし、それを取得しようとするかもしれませんが、もちろんそれは不可能であり、システムはそれを降伏させます。問題は、そのことです。問題は、降伏したスレッドがすぐにまた実行可能になってしまうことです ロックを保持しているスレッドよりも高い優先順位を持っているため、ロックを保持しているスレッドにはCPUランタイムを取得するチャンスがないのです。他のスレッドが実行時間を得るか、降伏したスレッドが実行時間を得るかのどちらかです。

なぜミューテックスではこの問題が起きないのでしょうか?優先順位の高いスレッドがミューテックスを取得できない場合、そのスレッドは降伏せず、少し回転するかもしれませんが、最終的にはスリープに追い込まれます。スリープしたスレッドは、あるイベント、例えば待ち望んでいたミューテックスのロックが解除されるなどのイベントによって起こされるまでは、実行することができません。Apple はその問題を認識しており、非推奨の OSSpinLock ということになります。新しいロックは os_unfair_lock . このロックは、異なるスレッド優先度クラスを認識するため、上記のような状況を回避することができます。もし、あなたのiOSプロジェクトでスピンロックを使うことが良いアイデアだと確信があるなら、そちらを使いましょう。を使わないでください。 OSSpinLock ! そして、どんなことがあっても、iOSに独自のスピンロックを実装してはいけません! macOS は、どのスレッドも(たとえ低プリオンスレッドであっても)CPU 時間が空くことを許さない別のスレッドスケジューラーを持っているので、この問題の影響を受けませんが、同じ状況が発生すると、非常に悪いパフォーマンスにつながります。 OSSpinLock は、macOSでも非推奨です。