web-dev-qa-db-ja.com

JavaScriptはシングルスレッドであることが保証されていますか?

JavaScriptは最近のすべてのブラウザ実装でシングルスレッド化されていることが知られていますが、標準で規定されているのか、それとも単に伝統的なものなのでしょうか。 JavaScriptが常にシングルスレッドであると仮定してもまったく安全ですか?

573
Egor Pavlikhin

それは良い質問です。 「はい」と言いたいです。できません。

JavaScriptは通常、スクリプト(*)から見える単一の実行スレッドを持っていると見なされます。そのため、インラインスクリプト、イベントリスナー、またはタイムアウトが入力された場合、ブロックまたは関数の最後から戻るまで完全に制御できます。

(*:ブラウザが実際に1つのOSスレッドを使用してJSエンジンを実装するかどうか、または他の制限された実行スレッドがWebWorkersによって導入されるかどうかの質問を無視します。)

しかし、実際には、これはまったくそうではありません、卑劣な厄介な方法です。

最も一般的なケースは、即時イベントです。コードが何らかの原因でブラウザを起動すると、ブラウザはこれらをすぐに起動します。

var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

IE以外のすべてでlog in, blur, log outになります。これらのイベントは、focus()を直接呼び出したために発生するだけでなく、alert()を呼び出した場合、ポップアップウィンドウを開いた場合、またはフォーカスを移動するその他の場合に発生する可能性があります。

これにより、他のイベントが発生することもあります。たとえば、i.onchangeリスナーを追加し、focus()呼び出しがフォーカスを外す前に入力に何かを入力すると、ログの順序はlog in, change, blur, log outになります。ただし、Operaではlog in, blur, log out, change、IEでは(それほど明確ではない)log in, change, log out, blur

同様に、それを提供する要素でclick()を呼び出すと、すべてのブラウザーですぐにonclickハンドラーが呼び出されます(少なくともこれは一貫しています!)。

(ここでは直接on...イベントハンドラープロパティを使用していますが、addEventListenerおよびattachEventでも同じことが起こります。)

nothingを実行したにもかかわらず、コードがスレッド化されている間にイベントが発生する状況もたくさんあります。例:

var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

alertを押すと、モーダルダイアログボックスが表示されます。そのダイアログを閉じるまで、スクリプトは実行されません。いや。メインウィンドウのサイズを変更すると、テキストエリアにalert in, resize, alert outが表示されます。

モーダルダイアログボックスが開いている間はウィンドウのサイズを変更することは不可能だと思うかもしれませんが、そうではありません。Linuxでは、ウィンドウを好きなだけサイズ変更できます。 Windowsではそれほど簡単ではありませんが、画面の解像度を大きいウィンドウからウィンドウが収まらない小さなサイズに変更すると、ウィンドウのサイズが変更されます。

おそらく、スクリプトがスレッド化されているためにユーザーがブラウザとアクティブに対話していないときに起動できるのは、resize(およびおそらくscrollのようなもの)だけだと思う​​かもしれません。また、単一のウィンドウの場合は正しいかもしれません。ただし、クロスウィンドウスクリプティングを行うとすぐにすべてがポットになります。いずれかがビジー状態のときにすべてのウィンドウ/タブ/フレームをブロックするSafari以外のすべてのブラウザーでは、別のドキュメントのコードからドキュメントを操作し、別の実行スレッドで実行し、関連するイベントハンドラーに火災。

スクリプトがまだスレッド化されている間に、発生させることができるイベントを発生させることができる場所:

  • モーダルポップアップ(alertconfirmPrompt)が開いているとき、Opera以外のすべてのブラウザーで。

  • showModalDialog中に、それをサポートするブラウザで。

  • 「このページのスクリプトはビジーである可能性があります...」ダイアログボックスは、スクリプトの実行を継続することを選択した場合でも、スクリプトの途中でサイズ変更やぼかしなどのイベントを起動して処理できますOpera以外のビジーループ。

  • しばらく前に、IEでSun Javaプラグインを使用して、アプレットのメソッドを呼び出すと、イベントが発生し、スクリプトを再入力できました。これは常にタイミングに敏感なバグであり、Sunがそれを修正した可能性があります(確かにそう願っています)。

  • おそらくもっと。これをテストしてからしばらく経ちましたが、それ以来ブラウザは複雑になりました。

要約すると、JavaScriptはほとんどの場合、ほとんどの場合、厳密なイベント駆動型の単一の実行スレッドを持っているように見えます。実際には、そのようなことはありません。これがどれだけのバグであり、どれだけ意図的な設計であるかは明確ではありませんが、複雑なアプリケーション、特にクロスウィンドウ/フレームスクリプトアプリケーションを作成している場合は、噛む可能性があります—そして断続的に、デバッグが難しい方法。

最悪の事態が最悪の事態に至った場合、すべてのイベント応答を間接化することで同時実行の問題を解決できます。イベントが発生したら、キューにドロップし、後でsetInterval関数で順番にキューを処理します。複雑なアプリケーションで使用することを意図したフレームワークを作成している場合、これを行うことは良い動きです。 postMessageは、将来のクロスドキュメントスクリプティングの苦痛を和らげることを願っています。

558
bobince

ブラウザのJavaScriptエンジンで非同期に実行すると、事実上すべての(少なくとも自明ではない)JavaScriptコードが壊れるためです。

それに加えて、 HTML5は既にWeb Workersを指定しています (マルチスレッドJavaScriptコード用の明示的で標準化されたAPI)が基本的なJavascriptにマルチスレッドを導入するという事実はほとんど意味がありません。

他のコメンターへのメモ:setTimeout/setInterval、HTTPリクエストオンロードイベント(XHR)、およびUIイベント(クリック、フォーカスなど)がマルチスレッドの粗雑な印象 - それらはすべて1つのタイムラインに沿って - 一度に1つずつ実行されるので、事前に実行順序がわからなくても、イベントの実行中に外部条件が変わることを心配する必要はありません。ハンドラー、時限関数、またはXHRコールバック。

111
Már Örlygsson

はい。ただし、setIntervalやxmlhttpコールバックなどの非同期APIを使用すると、並行プログラミングの問題(主に競合状態)が発生する可能性があります。

16
spender

はい、Internet Explorer 9ではメインスレッドでの実行に備えてJavascriptを別のスレッドでコンパイルします。ただし、これはプログラマーとしてのあなたにとって何も変わりません。

10
ChessWhiz

JavaScript/ECMAScriptはホスト環境内で動作するように設計されています。つまり、ホスト環境が特定のスクリプトを解析して実行し、JavaScriptを実際に役立つ環境オブジェクト(ブラウザのDOMなど)を提供しない限り、JavaScriptは実際には何もしません何でも

与えられた関数やスクリプトブロックは1行ずつ実行され、それはJavaScriptに対して保証されていると思います。しかし、おそらくホスト環境は同時に複数のスクリプトを実行することができます。あるいは、ホスト環境は常にマルチスレッドを提供するオブジェクトを提供することができます。 setTimeoutsetIntervalは、いくつかの並行処理を実行する方法を提供するホスト環境の例、または少なくとも疑似例です(厳密には並行処理ではない場合でも)。

7
Bob

実際には、親ウィンドウは、それ自体の実行スレッドが実行されている子ウィンドウまたは兄弟ウィンドウまたはフレームと通信できます。

7
kennebec

この仕様は、共有オブジェクトの状態にアクセスするために同期を実行するコードを必要とする、誰かがマルチスレッドでJavaScriptを実行するエンジンを作成することを妨げるものではないと思います。

私は、シングルスレッドのノンブロッキングパラダイムは、uiがブロックしてはいけないブラウザでjavascriptを実行する必要性から生まれたと思います。

Nodejsはブラウザのアプローチに従っています。

Rhino engineしかしながら異なるスレッドでjsコードを実行することをサポートします。実行はコンテキストを共有できませんが、スコープを共有できます。この特定の場合のためにドキュメンテーションは述べます:

... "Rhinoは、JavaScriptオブジェクトのプロパティへのアクセスがスレッド間でアトミックであることを保証しますが、同じスコープで同時に実行されるスクリプトについてはこれ以上保証しません。2つのスクリプトが同じスコープを同時に使用する場合スクリプトは共有変数へのアクセスを調整する責任があります

Rhinoのドキュメントを読むことで、私は誰かが新しいjavascriptスレッドを生み出すjavascript apiを書くことは可能であるかもしれないと結論を下しましたが、そのapiはrhino特有のものになるでしょう.

Javascriptで複数のスレッドをサポートするエンジンであっても、マルチスレッドまたはブロックを考慮しないスクリプトとの互換性があるはずです。

私が見ているように、ブラウザとノードを共感させる:

  • すべてのJSコードは単一スレッドで実行されますか? : はい。

  • JSコードは他のスレッドを実行させることができますか? : はい。

  • これらのスレッドはjs実行コンテキストを変えることができますか?:いいえ、できます(直接または間接的に(?))、イベントキューに追加できます。

つまり、ブラウザやnodej(そしておそらく他の多くのエンジン)の場合、javascriptはマルチスレッドではなくエンジン自体です。

5
Marinos An

いいえ.

私はここで群衆に反対するつもりですが、私と一緒に負担してください。単一のJSスクリプトは事実上単一スレッドになることを意図していますが、これは異なる解釈ができないという意味ではありません。

次のようなコードがあるとしましょう。

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

これは、ループの終わりまでに、リストには2乗されたインデックスである10000のエントリがなければならないという期待で書かれていますが、VMはループの各繰り返しが他に影響しないことに気づくでしょう2つのスレッドを使用して再解釈してください。

最初のスレッド

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

セカンドスレッド

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

ここでは単純化しています。JS配列は単純なメモリよりも複雑ですが、この2つのスクリプトがスレッドセーフな方法で配列にエントリを追加できる場合は、両方の実行が完了するまでにそれが得られます。シングルスレッド版と同じ結果です。

私はこのような並列化可能なコードを検出するVMを意識していませんが、状況によってはより高速になる可能性があるため、JIT VMでは将来的に登場する可能性があります。

この概念をさらに進めると、VMにマルチスレッドコードへの変換対象を知らせるためにコードに注釈を付けることができます。

// like "use strict" this enables certain features on compatible VMs.
"use parallel";

var list = [];

// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

WebワーカーがJavascriptを利用するようになってから、このようなより優れたシステムが実現することはまずありませんが、Javascriptが伝統的にシングルスレッドであると言っても差し支えないと思います。

4
max

@Bobinceは本当に不透明な答えを提供しています。

MárÖrlygssonの答えから離れて、Javascriptはこの単純な事実のために常にシングルスレッドです:Javascriptのすべてが単一のタイムラインに沿って実行されます。

それがシングルスレッドプログラミング言語の厳密な定義です。

4
williamle8300

さて、Chromeはマルチプロセスです、そして、私はすべてのプロセスがそれ自身のJavascriptコードを扱うと思います、しかしコードが知る限りでは、それは「シングルスレッド」です。

Javascriptでは、少なくとも明示的にはサポートされていないため、違いはありません。

3
Francisco Soto

@ bobinceの例を少し修正してみました。

<html>
<head>
    <title>Test</title>
</head>
<body>
    <textarea id="log" rows="20" cols="40"></textarea>
    <br />
    <button id="act">Run</button>
    <script type="text/javascript">
        let l= document.getElementById('log');
        let b = document.getElementById('act');
        let s = 0;

        b.addEventListener('click', function() {
            l.value += 'click begin\n';

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end\n';
            l.value += `result = ${s}, should be ${s2 + s2}\n`;
            l.value += '----------\n';
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize\n';
        });
    </script>
</body>
</html>

したがって、Runを押してアラートポップアップを閉じて「シングルスレッド」を実行すると、次のようになります。

click begin
click end
result = 20, should be 20

しかし、Windows上で安定したOperaまたはFirefoxでこれを実行し、画面に警告ポップアップを表示した状態でウィンドウを最小化/最大化すると、次のようになります。

click begin
resize
click end
result = 15, should be 20

これは「マルチスレッド」であるとは言いたくありませんが、予想外のコードが間違った時間に実行されたため、破損した状態になります。そしてこの振る舞いについてもっと知っておくと良いでしょう。

1