ステートマシンとスレッド


24

アラン・コックスはかつて「コンピューターは状態機械です。スレッドは状態機械をプログラムできない人々のためのものです」と言っていました
Alanに直接質問することは謙虚な選択肢ではないので、ここで質問します。1つのスレッドとステートマシンのみを使用して、Javaなどの高レベル言語でマルチスレッド機能をどのように実現しますか?たとえば、実行する2つのアクティビティ(計算とI / Oの実行)があり、1つのアクティビティがブロックできる場合はどうなりますか?
高レベル言語のマルチスレッド化に代わる「ステートマシンのみ」の方法を使用できるか?


4
コンピューターもチューリングマシンです。それでも、チューリングマシンのようにプログラムすることは必ずしも有用ではありません。命令型言語のスタックは非常に有用なものであり、マルチスレッドプログラムでは複数のスタックを同時にメモリに保持できます。ステートマシンで同じことを行うことは確かに可能ですが、さらに面倒です。
チトン

10
AlanはOSカーネル開発者でした。これは彼のドミアンでした。したがって、彼の引用はその文脈でとられるべきです。彼はそのようなモデルを使用する方が理にかなっている「金属に対して」プログラミングをしていたでしょう。OSがハードウェアとその固有の特性(「コンピューターはステートマシンである...」)を抽象化すると、ドメインでより意味のある他のモデルを使用できる機会と利益が得られます。ほぼすべてのゲームでステートマシンが多用されます。
スティーブンエバーズ

2
スレッドは、必要に応じて、ステートマシンスイッチの一部を自動的に管理するOSの機能です。もちろん、すべてを独自に管理する巨大なステートマシンを作成できますが、それはより複雑です。プロセスについても同じことが言えます。プロセスは、ステートマシンもプログラムできない人向けであると言えます。しかし、この抽象化により、はるかにシンプルでエラーが発生しにくいインターフェースが提供されます。私の意見では、これは聞いて熟考し、実際には無視されるべき「クールな引用」にすぎません。
ヤムマルコビッチ

2
「しかし、[スレッド]抽象化により、はるかにシンプルでエラーの少ないインターフェイスが提供されます。」それは間違っているようです。スレッドセーフが間違っている人の数は、それがエラーの原因と思われることを示しています。
-S.ロット

1
ここでのコメントと回答の多くは、引用を一般的にマルチタスク対策として解釈しています。アラン・コックスは単に反スレッドであり、複数のプロセスを使用して、人々がスレッドを使用する多くの目標を達成することを提唱するだろうと思います。彼はUnixハッカーであることに注意してください:fork FTW。彼からのコメントには直接コメントがありませんが、LinuxカーネルメーリングリストのLarry McVoyからのコメントは次のとおり
マーティンB

回答:


25

スレッドが行うことは、プロセスの一部が時間的にオーバーラップするように見えるようにインターリーブ操作のみです。複数のスレッドを備えたシングルコアマシンは、単にジャンプするだけです。1つのスレッドから小さなコードを実行してから、別のスレッドに切り替えます。単純なスケジューラーが、どのスレッドが最も優先度が高く、実際にコアで実行されるかを決定します。

シングルコアコンピューターでは、「同時に」実際に何も起こりません。すべてインターリーブされた実行です。

インターリーブを実現する方法はたくさんあります。たくさんの。

両方のスレッドが共通の変数に書き込むことができるように、単純なロックを使用する単純な2スレッドプロセスがあるとします。6つのコードブロックがあります。

  • T1ロック前
  • T1ロック付き
  • T1ロック後
  • T2ロック前
  • T2ロック付き
  • T2ロック後

[これは、ループ内にあるか、より多くのロックを持っている場合などです。複雑になるのではなく、長くなるだけです。]

T1のステップは順番に実行する必要があり(T1-before、T1-with、T1-after)、T2のステップは順番に実行する必要があります(T2-before、T2-with、T2-after)。

「順序」制約以外に、これらは任意の方法でインターリーブできます。とにかく。上記のように実行できます。別の有効な順序は(T1-before、T2-before、T2-lock、T1-lock、T2-after、T1-after)です。有効な順序は多数あります。

待つ。

これは、6つの状態を持つ単なるステートマシンです。

これは、非決定的な有限状態オートマトンです。T1-xxx状態とT2-xxx状態の順序は不定であり、問​​題ではありません。そのため、「次の状態」がコイントスである場所があります。

たとえば、FSMの起動時、T1 beforeまたはT2 beforeは両方とも正当な最初の状態です。コインを投げます。

T1以前にアップしたとしましょう。それを行う。それが完了すると、T1-withとT2-beforeの選択肢があります。コインを投げます。

FSMの各ステップには2つの選択肢(2つのスレッド-2つの選択肢)があり、コイントスでどの特定の状態に従うかを決定できます。


ありがとう、いい説明。そして、マルチコアマシンはどうですか?Javaステートマシンでコアを悪用する明示的な方法はないのでしょうか。そのためにOSに依存する必要がありますか?
ビクターソロキン

5
マルチコアマシンでは、スケジューリングが少し複雑になります。ただし、すべてのコアが単一の共通メモリに書き込むため、2つのコア間のメモリ書き込み順序は、本質的にメモリ書き込みのインターリーブ実行に戻ることを意味します。OSはコアを活用し、JVMはこれを活用します。それについてもう一度考える必要はありません。
-S.ロット

9

ブロッキング関数の作成は、ステートマシンを作成できない人向けです;)

スレッドは、ブロッキングを回避できない場合に役立ちます。基本的なコンピューターアクティビティが本当にブロックされているわけではありません。その多くは、使いやすさのためにそのように実装されています。読み取り関数は、文字を返すか「読み取りに失敗しました」の代わりに、バッファ全体が読み取られるまでブロックします。接続関数は、キュー内の戻りメッセージをチェックし、何も見つからない場合に戻る代わりに、応答を待ちます。

ステートマシン(少なくとも "フリーズ"を許可できないもの)でブロッキング関数を使用することはできません。

そして、はい、ステートマシンの使用は実行可能な代替手段です。リアルタイムシステムでは、これが唯一のオプションであり、システムはマシンのフレームワークを提供します。スレッドとブロッキング関数の使用は、「簡単な方法」です。通常、ブロッキング関数を1回呼び出すと、ステートマシンの約3〜4の状態が置き換えられるためです。


単一コンテキスト実行プログラムのコードページフォールトは、基本的に本当にブロックされます。定義上、単一の実行コンテキストを持つコードは、フロー内の次のコードチャンクが使用可能になるまで前進できません。
デビッドシュワルツ

1
@David Schwartz:確かに、それは根本的にブロッキングです。しかし、ブロックされたコードが実行するものではないため、「操作」ではありません。
ハビエル

1
ファイルの読み取りは基本的にブロックされません-常に、指定された場所の読み取りの要求と、要求が満たされた後のバッファーからのデータの取得に分割できます。また、ページフォールトは、無計画/ヒューリスティックスワップ使用の回避策です。実行に必要なすべてのデータが利用可能になる前に特定の状態になった場合に発生します。これは、先見性の欠如、状態マシンの概念に反するものです。スワップアウトおよびスワップイン操作がステートマシンの一部である場合、ページフォールトは発生しません。
SF。

1
@David Schwartz:「ブロッキング」動作の定義は、常に「リアルタイム」の要件に従います。例:コードページフォールトは、数百ミリ秒のオーダーで応答性を必要とするアプリケーションの非ブロッキングと見なされます。一方、アプリケーションに厳しいリアルタイム要件がある場合、仮想メモリはまったく使用されません。
-MaR

1
@Mar:...または、必要になる前に必要なデータのフェッチを保証する決定的スワップアルゴリズムを使用します。
SF。

9

1つのスレッドとステートマシンのみを使用して、Javaなどの高級言語でマルチスレッド機能を実現するにはどうすればよいですか?たとえば、実行する2つのアクティビティ(計算とI / Oの実行)があり、1つのアクティビティがブロックできる場合はどうなりますか?

あなたが説明しているものは、協調マルチタスクと呼ばれ、タスクにCPUが与えられ、自己決定した時間またはアクティビティの後に自発的にCPUを放棄することが期待されます。CPUの使用を継続したり、作品全体をブロックしたりしてハードウェアウォッチドッグタイマーを持たないことで連携しないタスクは、タスクを監視するコードがそれに対してできることは何もありません。

最近のシステムで見られるものは、プリエンプティブマルチタスクと呼ばれます。これは、ハードウェア生成の割り込みが到着したときにスーパーバイザーがCPUを放棄するため、タスクがCPUを放棄する必要がない場所です。スーパーバイザの割り込みサービスルーチンは、CPUの状態を保存し、次にタスクがタイムスライスに値すると見なされるときにそれを復元し、次に実行されるタスクから状態を復元し、何も起こらなかったようにそこにジャンプします。このアクションはコンテキストスイッチと呼ばれ、コストがかかる場合があります。

高レベル言語でのマルチスレッドの代わりに、「ステートマシンのみ」の方法を使用できるのでしょうか。

実行可能ですか?確かに。正気?時々。スレッドを使用するか、何らかの形の自作の協調マルチタスク(ステートマシンなど)を使用するかは、実行するトレードオフに依存します。

スレッドは、タスクの設計を単純化して、データスペースを他のユーザーと共有する独自のプログラムとして処理できるようにします。これにより、一度に繰り返し作業を行うために必要な管理やハウスキーピングのすべてではなく、手元の仕事に集中することができます。しかし、善行は罰せられないので、コンテキストスイッチでこの便利さのすべてを支払います。最小限の作業(自発的に、またはI / Oなどのブロックを行うこと)を行った後、CPUを生成するスレッドが多数あると、コンテキストの切り替えを行うプロセッサ時間が大量に消費されます。これは、ブロッキング操作が非常に長い間ほとんどブロックされない場合に特に当てはまります。

協力的なルートがより理にかなっている状況がいくつかあります。私はかつて、ポーリングを必要とするメモリマップインターフェイスを通じて多くのデータチャネルをストリーミングするハードウェア用のユーザーランドソフトウェアを作成する必要がありました。すべてのチャネルは、スレッドとして実行するか、単一のポーリングサイクルを繰り返し実行できるように構築されたオブジェクトでした。

マルチスレッドバージョンのパフォーマンスは、上記で説明したとおりの理由でまったく良くありませんでした。各スレッドは最小限の作業を行ってからCPUを譲り、他のチャネルに時間がかかるため、多くのコンテキストスイッチが発生しました。スレッドがプリエンプトされるまで無料で実行するとスループットが向上しますが、タイムスライスをすぐに取得できなかったため、ハードウェアでバッファーオーバーランが発生する前に一部のチャネルが処理されなくなりました。

各チャネルを繰り返し実行するシングルスレッドバージョンは、やけどした猿のように実行され、システムの負荷は岩のように低下​​しました。追加のパフォーマンスに対して支払ったペナルティは、タスクを自分で調整しなければならなかったことです。この場合、それを行うためのコードは十分に単純であり、開発と保守のコストはパフォーマンスを改善する価値があります。それが本当に一番下の行だと思います。私のスレッドが、システムコールが戻るのを待っているスレッドだったとしたら、おそらくそれだけの価値はなかったでしょう。

Coxのコメントを読んでください。スレッドは、ステートマシンを作成できない人専用ではありません。一部の人々はそれを非常に行うことができますが、ジョブをより早く、またはより複雑にしないために、缶詰のステートマシン(つまり、スレッド)を使用することを選択します。


2

実行する2つのアクティビティ(計算とI / Oの実行)があり、1つのアクティビティがブロックできる場合はどうなりますか?

まあ、私は正直にスレッドなしでブロッキングI / Oを処理する方法を想像することはできません。それを呼び出すコードが必要であるという理由だけで、結局ブロッキングと呼ばれwaitます。

オリジナルのCoxの電子メール(下記)を読んだところ、彼はそのスレッド化がうまくスケールしないと指摘しています。つまり、100個のI / O要求がある場合はどうなりますか?1000?10000?Coxは、スレッドの数が多いと深刻な問題につながる可能性があることを指摘しています。

From:Alan Cox(alan@lxorguk.ukuu.org.uk)
Date:Fri Jan 21 2000-13: 33 :52 EST

IBMの論文)、アプリケーションが膨大な数のスレッドに依存している場合、常にスケジューラーにぶつかり続けますか?多くの人が問題にたくさんのスレッドを投げます、そしてそれは本当に悪い設計でありえます。

それはあなたの心配の最小です。1000スレッドは8Mbのカーネルスタックであり、ほとんどのキャッシュをオフにできるようにタスクを十分に切り替えることができます。コンピューターはステートマシンです。スレッドは、ステートマシンをプログラムできないユーザー向けです。

Linuxが、特に非同期ブロックI / Oを特に助けていないケースはたくさんあります。

アラン

ソース:Re:IBMによるLinuxカーネルスレッドの興味深い分析(linux-kernelメーリングリストアーカイブ)


1
  • 理論的には、これは本当です。実際には、スレッドは、このようなステートマシンのプログラミングに使用される効率的な抽象化にすぎません。これらは非常に効率的であるため、ステートチャートおよびペトリネットのプログラミングにも使用できます(つまり、ステートマシンが基本的にシーケンシャルである並列動作)。

  • ステートマシンの問題は、組み合わせの爆発です。4G RAMを搭載したコンピューターの状態の数は2 ^(2 ^ 32)状態です(2Tディスクドライブはカウントしません)。

  • 唯一の道具がハンマーである男性にとって、すべての問題は釘のように見えます。


1

スレッドは、次の2つの場合の唯一のオプションです。

  • メモリを分離せずに複数のコアを使用します。
  • ブロックする外部コードに対処するため。

2番目の理由は、ほとんどの人がスレッドがIOまたはネットワークプログラミングを行うことを避けられないと考える理由ですが、これは通常、OSがより高度なAPIを持っていることを知らない(またはそれを使用して戦うことを望まない)ためです。

使いやすさと読みやすさに関しては、イベントループが常に存在します(libevEventMachineなど)、スレッドで行うのと同じくらい簡単にステートマシンのプログラミングを行う)同期の問題を忘れるのに十分な制御を提供します。


1
両方の長所:スレッド内の小さなブロッキングステートマシンは、非常にシンプルなアプリケーションコードを作成します。そして、もし持っていればスレッドはうまくコアに分割されます。すべてに少なくとも2つのコアがない場合は、すぐにそうなります。すなわち、2012
。–ティムウィリスクロフト

0

ステートマシンとマルチスレッドの相互作用を理解する良い方法は、GUIイベントハンドラーを調べることです。多くのGUIアプリケーション/フレームワークは、可能な入力ソースをポーリングし、受信した各入力に対して関数を呼び出す単一のGUIスレッドを利用します。基本的に、これは巨大なスイッチとして書くことができます。

while (true) {
    switch (event) {
        case ButtonPressed:
        ...
        case MachineIsBurning:
        ....
    }
}

ここで、この構造の高レベル制御のレベルを高くできないことがすぐに明らかになります。ButtonPressedのハンドラーは、ユーザーの操作なしで終了し、メインループに戻る必要があります。処理できます。保存する状態がある場合、この状態はグローバル変数または静的変数に保存する必要がありますが、スタックには保存しないでください。つまり、命令型言語の通常の制御フローは制限されています。基本的にステートマシンに制限されます。

再帰レベルなどを保存する必要があるサブルーチンをネストしている場合、これはかなり面倒になります。または、ファイルの読み取り中ですが、ファイルは現在使用できません。または、単に長い計算中です。これらすべての場合において、現在の実行の状態を保存してメインループに戻ることが望ましくなります。これはマルチスレッドです。これ以上でもそれ以下でもありません。

プリエンプティブマルチスレッドの導入(つまり、オペレーティングシステムがスレッドを制御するタイミングを決定する)の導入により、全体が少し複雑になりました。そのため、今日では接続がすぐに明確になりません。

したがって、最後の質問に答えるために、はい、ステートマシンは代替手段であり、ほとんどのGUIはGUIスレッドでそのように機能します。ステートマシンを押しすぎないでください。すぐにメンテナンスできなくなります。


0

ステートマシンの使用が高水準言語で実行可能かどうかを尋ねることは、アセンブラーでの記述が高水準言語の使用に代わる実行可能な代替手段であるかどうかを尋ねることに少し似ています。適切な状況を考えると、彼らは両方とも自分の場所を持っています。

スレッドを使用するという抽象化により、より複雑な並列システムの実装が容易になりますが、最終的にはすべての並列システムに同じ問題があります。デッドロック/ライブロック優先順位の反転などの古典的な問題は、十分に複雑な場合、共有メモリパラレルNUMA、またはCSPベースのシステムと同様に、ステートマシンベースのシステムで可能な限り発生します。


0

確かに、ステートマシンは非常に「エレガントな」コンピューティングコンセプトですが、おっしゃるように、かなり複雑です。そして、複雑なことを正しく行うのは困難です。そして、正しくないものは壊れているだけなので、あなたがアラン・コックスの推定身長の天才でない限り、あなたが働くと知っているものに固執してください-「賢いコーディング」を学習プロジェクトに任せてください。

誰かがそれをしようとして無駄に試みたとき、それを維持することになると(それがうまく機能すると仮定すると)、タスクが不可能に近いことがわかります。元の「天才」は、ほとんど理解できないコードの塊を残して移動します(これらのタイプの開発者は、技術文書はもちろん、あまりにも多くのコメントを残さない傾向があるため)。

いくつかのケースでは、ステートマシンの方が良い選択です-現在、いくつかのステートマシンパターンが使用され、より正式な方法で繰り返し使用される埋め込み型のものを考えています(つまり、適切なエンジニアリング:))

スレッド化を正しく行うのも難しい場合がありますが、主にスレッド間でデータを共有する必要性を減らすことで、役立つパターンがあります。

これについての最後のポイントは、最新のコンピューターはとにかく多くのコアで実行されるため、ステートマシンが利用可能なリソースを十分に活用しないことです。ここではスレッド化がより良い仕事をすることができます。


2
ステートマシンはまったく複雑ではありません。複雑なステートマシンは複雑ですが、すべての複雑なシステムもそうです:o)
11

2
「試行しない」の場合は-1。それはあなたが与えることができる最悪のアドバイスです。
ハビエル

1
-1「試してはいけない」?それはただ愚かです。また、ステートマシンがハードであるというあなたの主張に挑戦します。Heirarchal Fuzzy State Machineのようなものに入ると、それからはい、もう少し複雑になります。しかし、単純な状態マシンですか?それは私が学校にいたときに2年ごとに学んだ非常に基本的なことです。
スティーブンエバーズ

「やってはいけない」ビットを言い換えてみましょう
...-gbjbaanb

0

スレッドの代わりに適切なステートマシンを使用する良い例:nginx vs apache2。通常、nginxは1つのスレッドですべての接続を処理し、apache2は接続ごとにスレッドを作成すると想定できます。

しかし、私にとって、ステートマシンとスレッドの使用は、完全に手作りされたasmとjavaを使用した場合と非常に似ています。他の多くのプログラマー。あなたが高速なウェブサーバーを作りたいと思っているなら、状態マシーンと非同期IOを使用してください。プロジェクトを作成している場合(どこでも使用するライブラリではありません)-スレッドを使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.