質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.93%

c10k問題:第二次プリエンプティブ戦争

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 2,933

score 3280

また、具体的な課題が含まれていないご意見募集型の質問で失礼します。

昔話

Windows 3.1 や Mac OS System 4 など昔のパソコンのOSはノンプリエンプティブ・マルチタスク(以降、協調的マルチタスクと呼びます)でした。
これらのOSではアプリケーションが自発的にCPUを開放しないとタスクが切り替わらず、一つでもCPU の開放のしかたが悪いプログラムがあると、OSごと凍りついてしまうものでした。そのことが不評で、今や全面的にプリエンプティブ・マルチタスクのOSに置き換えられました。

帰ってきた協調的マルチタスク

今、マルチスレッドのアーキテクチャが抱えるC10K問題に対応するために node.js、 nginx+lua などイベント駆動型やコルーチン型(=協調的マルチタスク)で処理する処理系が出始めました。そして、従来の一つのリクエストを一つのスレッドが処理するプリエンプティブなマルチスレッド処理系に比べて高い性能が出るということが定説になっています。

また戦いが

その状況の中で、最近のWebでの議論を見ていると、Linux のマルチスレッドが非常に軽量・高速で協調的マルチタスクじゃなくてもマルチスレッドで十分性能が出るというベンチマーク結果が出てきているようです(※1)。そうなると、協調的マルチタスクの処理系はまたなくなってしまうのでしょうか。
前述の昔話において、パソコンのOSがプリエンプティブマルチタスクに置き換えられるときも、OSの実装者は「プリエンプティブマルチタスクなんて必要ない。プログラマが時間のかかる処理を実装するときに処理の途中経過を報告したり、中断したりする機能をちゃんと入れてれば、協調的マルチタスクのほうが効率が高いに決まっている」と執拗に粘ったのですが、世の中の流行には逆らえず(というよりも質の悪いプログラムの多さに負けて)、負けてしまいました。
今回もプリエンプティブマルチタスク(マルチスレッド派)対協調的マルチタスク(イベント駆動型やコルーチン派)の戦いが始まっているように思います。これを第二次プリエンプティブ戦争と呼びましょう。

協調的マルチタスク派

(いくつか回答いただいて、自分の好みの問題とC10K問題が直結していないことに気が付き、書き直しました)
私は個人的にはシングルスレッドイベント駆動型でコーディングするのが好きで、c10K問題を理由にマルチスレッドプログラミングのパラダイムを駆逐して欲しいくらいに思っています。たとえば、DBへの問い合わせが長い時間かかる場合に、ユーザからの要求やシステムのシャットダウンに対応して問い合わせを中断できるようにする場合、マルチスレッドで割り込みを気にしながら順次的に書くよりも自分で状態遷移を管理するほうが簡単ではないでしょうか。マルチスレッドの場合、
(DBへの問い合わせを中止できるようなドライバがあるかどうかはおいておいて)

class 問い合わせ {
  do() {
    try {
      DBへの問い合わせ
    } catch (CancelSignal cs) {
      if (微妙なタイミング判定) {
        問い合わせ中止処理
      }
    }
  }
  onCancel() {
    問い合わせ実行中スレッドへ CancelSignal を送信
  }
}

となり、問い合わせの終了とキャンセルシグナルの入力との微妙なタイミング判定(排他制御)が必要ですが、シングルスレッドであれば、

class 問い合わせ {
  do() {
    DBへの問い合わせ((event) => this.handler(event));
  }
  handler (event) {
    ...
  }
  onCancel() {
    問い合わせ中止処理
  } 
}

と書けて、シングルスレッドなので、handler 呼び出しと onCancel の呼び出しは早い者勝ちとなるわけです。

そもそも、OSレベルでは多重入力はイベント駆動で実装されているわけで、そのイベント駆動をそのままコーディングできるシングルスレッドイベント駆動プログラミングって気持ち良くないですか?

質問:シングルスレッドプログラミングは好きですか?残ると思いますか?

いくつか回答いただいて、シングルスレッドプログラミングとC10K問題が直結していないことに気が付きましたが、質問としてはC10K問題で復権したシングルスレッドプログラミングをどう思うかというポイントに変更して続けさせていただきたいと思います。
JavaScript のコールバックは嫌いですか? lua などコルーチンの yield って懐かしくないですか?マルチスレッドプログラミングって難しくないですか?

補足1:マルチコアについて

それほど性能がCPUに依存するアプリが少ないのではないかとは思いますが、、CPU依存が強い場合でもワーカプロセスに負荷分散することで、プログラム自身はシングルスレッド・協調的マルチタスクで書くことが可能だと思います。特に並列演算で問題が解決するような場合は、ワーカプロセス側で GPUをつかったり、SIMD命令を使うなど、マルチコア以上に並列度をあげる工夫が必要ではないかと考えます。この場合は、マルチスレッドプログラミングというよりも並列処理プログラミングになってくると思います。となると、やはりマルチスレッドプログラミングは不要なのではないかと思えてきます。

補足2:イベント駆動型プログラミングの順次的記述

回答でイベント駆動型プログラミングのデメリットとしてサブルーチンのネスト構造などモジュール化がやりにくいという指摘がありました。 Javascript では、サブルーチンの呼び出し後の継続処理をコールバック関数(というかクロージャ)に記述するという技法で回避し、非同期でありながらモジュール化を行うことに成功しています。しかし、この技法はコールバック地獄と呼ばれており、モジュール化には成功しましたが、順次処理を記述すると、ネストがどんどん深くなるという欠点がありました。次世代 Javascript である es6 では、この問題を Promise という技法でさらに回避しております。
ここで、普通に順次的に記述したプログラムと Promiseを使って非同期処理可能でありながら順次的に記述したプログラムを比較してみます。

普通の順次的記述

function main() {
  let x = 'default value of x';
  let y = 'default value of y';
  let z;
  try {
    z = getValuesForZ(x);
    z = z.map(item => processItem(item));
    if (z.includes(processItem(x))) {
      y = processY(y);
    }
    console.log("x = " + x + ", y= " + y + ", z= " + z);
  } catch (e) {
    console.log(e);
  }
}

function getValuesForZ(arg)
{
  return ['value1', 'value2'].concat(arg);
}

function processItem(item) {
  return 'processed:' + item;
}

function processY(y) {
  return processYnest(y);
}

function processYnest(y) {
  return 'processed in nested function:' + y;
}

main()

Promise を使った非同期処理可能な順次的記述

function main() {
  Promise.resolve({
    x: 'default value of x',
    y: 'default value of y'
  })
  .then(getValuesForZ())
  .then(state => {
    return Promise.all(state.z.map(item => Promise.resolve(item).then(processItem())))
      .then(z => Object.assign(state, {z}));
  })
  .then(state => {
    return Promise.resolve(state.x).then(processItem()).then(r => {
      if (state.z.includes(r)) {
          return Promise.resolve(state).then(processY());
      }
      return state;
    });
  })
  .then(state => {
    console.log("x = " + state.x + ", y= " + state.y + ", z= " + state.z);
  })
  .catch(e => {
    console.log(e);
  });
}

function getValuesForZ()
{
  return state => Object.assign(state, {z:['value1', 'value2'].concat(state.x)});
}

function processItem() {
  return item => 'processed:' + item;
}

function processY() {
  return state => {
    return Promise.resolve(state.y)
      .then(processYnest())
      .then(y => Object.assign(state,{y}));
    };
}

function processYnest() {
  return y => 'processed in nested function:' + y;
}

main()

普通版は 34行に対して、Promise 版は48行と行数は増えてしまいましたが、処理順にプログラムを書くことができて、かつ、サブルーチンによるモジュール化もできていると思います。
モジュール化のみそは、サブルーチンが演算結果を返すのではなく、演算を行う関数を返すことです。このようにすることで、呼び出し側は非同期にその関数を呼び出すことができるわけです。
プログラムの詳細な説明は避けますが、順次処理、条件判定、繰り返し処理、サブルーチンのネスト呼び出しを含める例となっています。プログラムの処理内容には何の意味もありません。また、両方を比較しやすいように非同期処理は入っていませんが、Promise版の方ではいくらでも非同期処理を挿入できますが、普通版では非同期処理を挿入することは不可能です。

※1の参考:
TCP/IP - Solving the C10K with the thread per client approach
「サーバ書くなら epoll 使うべき」は、今でも正しいのか
いずれも、協調的マルチタスクに比べれば性能は出ないと思いますが、「協調的マルチタスクがないと1万コネクションはできないのかというとそうではない」という主張だと思います。桁が上がって10万コネクションになれば、やはり協調的マルチタスクが必要なのかもしれません。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • 退会済みユーザー

    2016/10/29 15:36

    こちらの質問が他のユーザから「問題・課題が含まれていない質問」という指摘を受けました
    teratailでは、漠然とした興味から票を募るような質問や、意見の主張をすることを目的とした投稿は推奨していません。
    「編集」ボタンから編集を行い、質問の意図や解決したい課題を明確に記述していただくと回答が得られやすくなります。

  • mit0223

    2016/10/29 19:50

    yohhoy様、一応ソースを追加しましたが、補足のとおり、OSスレッドで協調的マルチタスクと同等に性能が出るという話ではありませんでした。誤解を招く表現ですみませんでした。

    キャンセル

  • yohhoy

    2016/10/30 00:38

    参考URLありがとうございました。思っていた以上に"動く"ものなのですね(私の知識が古かったようです)。とはいえStackOverflow回答にあるように、OS側のリソース制限値をチューニングする必要はあるようですね。

    キャンセル

回答 2

checkベストアンサー

+3

こんにちは。

質問:どっちが好きですか・どっちが勝つと思いますか

マルチ・スレッド型が好きです。そしてマルチ・スレッドの経験の方が遥かに多いです。少なくとも私の経験上は、多くの処理は「流れ」で把握するのでイベント駆動型が適しているなケースは稀でした。
ヒョイっと「待ち」を作れないイベント駆動型でシーケンス制御的な処理を行うのはかなり辛いです。

しかし、どちらが勝つというものではないです。
例えば、GUIにはイベント駆動型は適していると思います。そして、イベント駆動型やコ・ルーチンもスレッドの上で動作します。マルチ・コアを活かすためにスレッドを複数同時起動します。

そして、今後も、マルチ・コアが主流のまま、コアの数が増えていくと思います。
熱問題のためCPUの周波数を大きく上げることは困難だそうです。その状況で処理性能を上げるためのマルチ・コアですね。そして、マルチ・コアを有効に利用するためのマルチ・スレッドです。
本質的にノンプリエンプティブなプログラミング・モデルだけではマルチ・コアを有効利用できませんから、これらの基本的な部分にブレークスルーが起きない限り、状況は変わらないと考えています。

更に、このあたりに革新が起きても、OSがノンプリエンプティブに戻ることはないと考えています。
「プログラマが時間のかかる処理を実装するときに処理の途中経過を報告したり、中断したりする機能をちゃんと入れてれば」の前提条件をアプリが満たしていることを検証する必要が有ります。
そもそも検証出来るわけがないデバッグ中は特に辛いです。暴走→システム再起動。二昔前によくやってました。その悪夢の復活です。
また、高信頼性を求められないちょっと便利なアプリもあります。そのようなアプリを本番稼働中のシステムで起動するためだけのために、追加でOS並の密なテストを行いCPU開放を適切に行うことの検証を行わないと、システム全体の信頼性を損なうことになります。
そんな面倒な運用が必要なシステムより、プリエンプティブでOS側がアプリに引きずられて落ちないようなシステムが主流になるのは必然と思います。


【C10K問題について】
現場を知らないのですが、C10K問題については、64bit OSでスタックフルなコ・ルーチンがバランスの取れた解のように感じてます。実際のところ、どの方向が主流なのでしょう?
64bit OS全盛の現在でも未だにスタックレスが主流でしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/02 23:43

    > CPU依存が強い場合でもワーカプロセスに負荷分散することで、プログラム自身はシングルスレッド・協調的マルチタスクで書くことが可能だと思います。

    ワーカプロセスとワーカスレッドの相違はメモリ空間が分離されているかいないかが主です。
    前者は空間切り替えが発生するため、あまり密に排他制御すると効率が悪くなりますので疎結合で使います。密なやり取りをする時には空間切り替えのないワーカスレッドを用いると性能が改善します。
    そしてワーカプロセスもスレッドを持っていますから、ワーカプロセスを使うということはマルチ・スレッドでもあります。
    ワーカプロセスとのやり取りで共有メモリを使う場合、その共有メモリに関してはマルチ・スレッドの場合と同様な排他制御が必要になります。逆に、実際に使うかどうかは別ですが、プロセス間通信を使ってスレッド間でも通信できます。

    Promiseの例は良く分かりませんでしたが、「普通の順次的記述」の方がメンテナンス性は高いと感じます。そして、マルチ・スレッドを使えばこのような記述を使いつつ複数の要求を処理できます。
    更に、getValuesForZ()、processY()、processYnest()のような処理は直接main()の中に記述することもできます。1箇所からしか呼ばれない短い処理はサブルーチンにしない方がメンテナンス性は高いのでより好ましいです。
    しかし、イベント駆動型でこれらを非同期に実行するためにはサブルーチンにせざる得ないでしょう。
    こんなに簡単な例でこれだけ差が付くのでしたら、実プロジェクトで付く差は凄いことにならないでしょうか?

    キャンセル

  • 2016/11/03 09:24

    マルチスレッドのメリットは理解しているつもりです。しかし、c10kにおけるマルチスレッドの問題は、高性能なネットワーク環境とハードウェアがもたらす大量のリクエストのルーティングで、スレッドごとのメモリとコンテキストスイッチが性能ボトルネックになることです。
    それで、リクエストのルーティングとリソースクリティカルな処理(DBMS、HPC系ワーカ)でスレッド/プロセスを分けることで回避するわけですが、一般のプログラマが主にコーディングするのは、ビジネスルールであり、リクエストのルーティングのほうに含まれるのではないでしょうか?
    DBMSを開発する人は当然マルチスレッドでプログラミングすべきですし、HPC系のワーカをコーディングする場合は、マルチスレッドのみならず、並列処理プログラミングが扱える必要があります。しかし、これらの仕事をするプログラマは少数ではないでしょうか。
    というのが出発点です。

    > こんなに簡単な例でこれだけ差が付くのでしたら、実プロジェクトで付く差は凄いことにならないでしょうか?

    否定しません。といいますか、この質問の論点そのものであり、「この Promise ってどうなのよ!」っていうのが私の気持ちです。

    Java Servlet 3.0 のようにリクエスト受付用非同期シングルスレッドとリソースクリティカルな処理を受け持つ同期的ワーカマルチスレッドで構成するのが良いかもしれません。
    同じ協調的マルチタスクでも Goや erlang のように軽量スレッドで、プログラマから見るとマルチスレッド的に見えても性能が出るというのもいいかもしれません。(余談ですが、 Go は関数を呼び出すと自動的に yield がかかるらしい)

    しかし、Webアプリにおいては、クライアント側は Javascript しかなく(プログラマに選択権はない)、Webアプリのプログラマは、どのみち Promise 的な技を覚える必要があるわけです。で、今のところ、私は他人に聞かれたら、クライアント側と共通で使えるフレームワークであるイベント駆動型シングルスレッドプログラミングを覚えて、node.js でサーバ側のビジネスロジックもそれで書けば良いのでは?と答えています。

    キャンセル

  • 2016/11/03 11:26 編集

    > スレッドごとのメモリとコンテキストスイッチが性能ボトルネックになることです。

    そのボトルネックをアプリケーション・プログラム開発工数を大きく増やして回避するのがベストなのかについては議論があると思います。アプリ開発に投資するより、ハードウェアやミドルウェアに投資してより高速なサーバやよりたくさんのサーバを導入した方が合理的と判断するマネージャも居ると思います。
    エコではないですし、プログラマの仕事が減るので必ずしも嬉しい判断ではないですが、開発費の方が大きい場合は妥当な判断と思います。

    > それで、リクエストのルーティングとリソースクリティカルな処理(DBMS、HPC系ワーカ)でスレッド/プロセスを分けることで回避するわけですが、一般のプログラマが主にコーディングするのは、ビジネスルールであり、リクエストのルーティングのほうに含まれるのではないでしょうか?

    マルチ・スレッドの場合、ビジネスルール部分を同期型でコーディングできますのでメンテナンスしやすいです。
    上級のプログラマがマルチ・スレッド対応する部分を設計・開発し、プログラミング規約を定めておけば一般のプログラマはマルチ・スレッド設計から開放されます。
    といいますか、従来はその部分はミドルウェアが担っていたのですよね? そのままで良いのでは?

    見積もりには量が少なすぎますがPromiseのサンプルの場合は同期型に比べて4割もステップ数が増えているので4割近く開発費が増える可能性もあります。(直感的には妥当な印象です。)
    その分、ハードウェアに投資した方が安く済みそうな印象を受けます。

    キャンセル

0

質問:どっちが好きですか・どっちが勝つと思いますか

一旦個人の好き嫌いは脇におきます。

マルチスレッド/非協調的なプリエンティブ並行処理モデル と イベント駆動/協調的なノン・プリエンティブ並行処理モデル は勝ち負けではなく、両者を組み合わせて利用すべきものと考えます。また、一般論としてはマルチスレッド・モデルの方が、イベント駆動に比べてプログラミングが簡単になるはずです(私の経験則とも合致します)。

私は個人的にはイベント駆動型が好きで、性能問題でマルチスレッド派を駆逐して欲しいくらいに思っています。

性能問題にフォーカスした場合、(あなたの言うイベント駆動型=シングルスレッド・モデルであれば、)イベント駆動型はプロセッサのマルチコア化に追従できません。イベント駆動でもマルチプロセス・アプリケーションであればスケーラビリティを得られますが、そもそもマルチプロセスは非協調並行処理ですからマルチスレッドと組み合わせるのと同じことです。結局のところ、マルチプロセス化はイベント駆動と非協調処理を組み合わせているにすぎません。

JavaScript のコールバックは嫌いですか?lua などコルーチンの yield って懐かしくないですか?

上記の追加質問と、それまでの論点との関連性がイマイチ不明瞭ですが...


改訂後の質問内容へのコメント:

OSレベルでは多重入力はイベント駆動で実装されているわけで、そのイベント駆動をそのままコーディングできるシングルスレッドイベント駆動プログラミングって気持ち良くないですか? 

JavaScript/Node.jsのように綺麗なイベント駆動モデルなら(それなりに)賛同できますが、OSレベルにより近いselectやepollによるイベント駆動処理は、とにかく面倒でプログラミングも苦痛です。このあたりはlibuv (Node.jsも同ライブラリ利用)のようなイベント駆動フレームワークが、低レイヤにおける暗部を覆い隠している恩恵と思います。

一方でマルチスレッドモデルとして挙げている例は、「微妙なタイミング判定」で確かに排他制御が必要なものの、本質的には「いつ割込判定を行うか」という点のみを問題視しているように見えました。イベント駆動フレームワークは、この判定タイミングを「イベントループ」という形で標準化しただけとも言えます。

そういう意味では、マルチスレッドで性能と柔軟性を優先する(設計・デバッグ難易度は上がる)か、イベント駆動で標準化された構造を好む(自由度は低くマルチコア非対応)かという話なのかもしれません。

マルチスレッドプログラミングって難しくないですか? 

はい。非同期プログラミングは 本質的に 難解です。これはイベント駆動モデルであっても、複数I/Oを制御するような非同期処理メインでは似たような難しさがあると思います。イベント駆動をマルチスレッド処理と比べたときの利点は、非決定的要因(≒タイミングに依存した処理結果)が比較的少ないという点でしょうか。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/10/31 18:37

    回答を編集いただきありがとうございます。大変参考になります。まだ、少し議論を続けたいのですが、その前に1点引っかかる部分がありまして、ご回答いただければと思います。

    > マルチスレッドで性能と柔軟性を優先する

    現在の化物みたいなCPUで、ソートや結合はDBMSで実行される前提で、アプリケーションサーバ上のビジネスロジックの演算がCPU依存になるようなアプリケーションは少ないのではないかと思いますが、いかがでしょうか?また、CPU依存の性能問題が出たとして、それがマルチコアでスケールすることで解決する(=ちょうど一桁くらい足りない)ようなケースも稀ではないかと思いますが、いかがでしょうか? teratail でも演算性能を重視するような質問をよく見かけるのですが、いつも疑問に思っています。

    キャンセル

  • 2016/10/31 18:59

    対象とする分野に依存するとしか答えられませんね... いわゆる3階層システムな「業務アプリ」(という表現が適切かはわかりませんが)ではCPU boundなケースは少ないと思います。一方で、大量のデータをもとに"演算"するアプリケーション領域は確かに存在しており、そこではマルチコアによる水平スケーリング性能が非常に重要なファクタになります。程度問題はありますが、後者はHPC(High Performance Computing)寄りですね。

    キャンセル

  • 2016/10/31 19:13

    ありがとうございます。たしかに銀の弾丸はないとは思います。しかし、巷でよく見る性能問題がSQLのTips系とかコンテキストスイッチ多発系のものが多いにもかかわらず、まだCPUに性能を求めている人が多いなぁと思いまして・・・

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.93%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る