複数のスレッドがあり、すべてのスレッドがほぼ同時に同じ関数を呼び出すとします。
いつでも関数のインスタンスを 1 つだけ許可する呼び出し規約はありますか? つまり、2 番目のスレッドによって呼び出された関数は、最初のスレッドによって呼び出された関数が戻った後にのみ開始されるということです。
または、これらの呼び出し規約はコンパイラ固有のものですか? それらを使用した経験はあまりありません。
複数のスレッドがあり、すべてのスレッドがほぼ同時に同じ関数を呼び出すとします。
いつでも関数のインスタンスを 1 つだけ許可する呼び出し規約はありますか? つまり、2 番目のスレッドによって呼び出された関数は、最初のスレッドによって呼び出された関数が戻った後にのみ開始されるということです。
または、これらの呼び出し規約はコンパイラ固有のものですか? それらを使用した経験はあまりありません。
(スレッドマンボジャンボを気にしない場合は、一番下までスキップしてください)
前述のように、これは「呼び出し規約」ではなく、コンピューティングの一般的な問題である同時実行性です。また、一度に 2 つ以上のスレッドが共有ゾーンに入る可能性があり、異なる結果になる特定のケースは、競合状態と呼ばれます(また、電子機器やその他の領域との間で拡張されます)。
スレッド化の難しい点は、コンピューティングが非常に決定論的な問題であるということですが、スレッド化が関与すると、プラットフォーム/OS ごとに異なるある程度の不確実性が追加されます。
スレッドが 1 つの場合は、すべてのタスクを常に同じ順序で実行できることが保証されますが、複数のスレッドがあり、順序がタスクを完了する速度に依存する場合は、CPU を使用したい他のアプリケーションを共有し、次に基盤となるハードウェアが結果に影響します。
個々のケースに対処するためのテクニック、ツール、およびライブラリがあるため、「スレッド化を行うための確実な方法」はあまりありません。
最もよく知られている手法はセマフォ (またはロック) を使用するものであり、最もよく知られているセマフォはミューテックス 1 であり、一度に発生する一種の「フラグ」を持つことによって、一度に 1 つのスレッドのみが共有スペースにアクセスできるようにします。スレッドが入りました。
if (locked == NO)
{
locked = YES;
// Do ya' thing
locked = NO;
}
上記のコードは動作するように見えますが、両方のスレッドが変数を渡してif ()
から変数を設定する場合 (スレッドが簡単に実行できる) に対しては保証されません。したがって、この種の操作にはハードウェア サポートがあり、1 つのスレッドのみが実行できることが保証されtestAndSet
ます。つまり、変数をチェックし、利用可能な場合は設定する操作です。(これは命令セットのx86命令です)
ロックとセマフォと同じ流れで、複数のリーダーと 1 つのライターを許可する読み取り/書き込みロックもあり、揮発性の低いものに特に役立ちます。他にも多くのバリエーションがあり、X 個のスレッドなどを制限するものもあります。
しかし、全体として、ロックは基本的にマルチスレッドのシリアル化を強制しているため、ロックは不自由です。スレッドは実際にロックを取得しようとしてスタックする必要があります (または単にテストして終了する必要があります)。複数のスレッドを持つという目的をちょっと破りますよね?
スレッド化に関する最善の解決策は、スレッドが使用する必要がある共有スペースの量を最小限に抑えることです。おそらく完全に削除します。おそらくrwlocks
、ボラティリティが低いときに使用し、ロックがかかっているかどうかを確認し、そうでない場合は立ち去る、「試行して残す」種類のスレッドを試してみてください。
OS の先生がかつて (禅のように) 言ったように、「最善のロックは回避できるものです」。
現在、スレッド化は難しく、それを回避する方法はありません。そのため、この種の問題に対処するためのパターンがあり、スレッド プール パターンは、少なくとも iOS では Grand Central Dispatch (GCD) の導入以来、人気のあるパターンです。
大量のスレッドが乱暴に実行され、あらゆる場所でキューに入れられる代わりに、一連のスレッドを作成し、「プール」でタスクを待機し、実行することのキューを作成します。理想的には、それぞれのタスクがオーバーラップしないようにします。他の。
さて、スレッドパターンは以前に議論された問題を解決しませんが、精神的に対処しやすくするためにパラダイムを変更します. 「これこれを実行する必要があるスレッド」を考えるのではなく、「実行する必要があるタスク」に焦点を切り替えるだけで、どのスレッドがそれを実行しているかは問題になりません。
繰り返しますが、プールによってすべての問題が解決されるわけではありませんが、理解しやすくなります。そして、理解しやすいことは、より良い解決策につながる可能性があります。
上記の理論的なことはすべて、POSIX レベル (semaphore.h、pthreads.h など。pthreads には非常に優れた r/w ロック機能があります) で既に実装されています。それらについて読んでみてください。
(編集: このスレッドは単純な C ではなく Obj-C に関するものだと思い、Foundation と GCD のすべてを編集しました)
呼び出し規約は、スタックとレジスタを使用して関数呼び出しを実装する方法を定義します。各スレッドには独自のスタックとレジスタがあるため、スレッドの同期と呼び出し規約は別のものです。
複数のスレッドが同じコードを同時に実行するのを防ぐには、ミューテックスが必要です。関数の例では、通常、スレッドを同時に実行させたくないステートメントの周りに、関数のコード内にミューテックス ロックとロック解除を配置します。
大まかに言うと、関数呼び出しを含むプレーン コードはスレッドを認識しませんが、オペレーティング システムは認識します。ミューテックスを使用すると、スレッドの実行を管理するシステムを利用できます。詳細については、Google で検索するだけです。
新しい C 標準リビジョンである C11 には、マルチスレッドのサポートが含まれていることに注意してください。しかし、これは一般的な概念を変更しません。これは単に、オペレーティング システム固有の関数の代わりに C ライブラリ関数を使用できることを意味します。