JavaScriptは、すべての最新のブラウザー実装でシングルスレッドであることが知られていますが、それは標準で指定されているのですか、それとも伝統によるものですか?JavaScriptが常にシングルスレッドであると仮定することは完全に安全ですか?
JavaScriptは、すべての最新のブラウザー実装でシングルスレッドであることが知られていますが、それは標準で指定されているのですか、それとも伝統によるものですか?JavaScriptが常にシングルスレッドであると仮定することは完全に安全ですか?
回答:
それは良い質問です。「はい」と言いたいです。できません。
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">
log in, blur, log out
IEを除くすべての結果。これらのイベントはfocus()
、直接呼び出しただけで発生するのではなく、を呼び出しalert()
たり、ポップアップウィンドウを開いたり、フォーカスを移動したりするために発生する可能性があります。
これにより、他のイベントが発生する可能性もあります。たとえば、i.onchange
リスナーを追加し、focus()
呼び出しがフォーカスを外す前に入力に何かを入力すると、ログの順序はlog in, change, blur, log out
になります。log in, blur, log out, change
log in, change, log out, blur
同様click()
に、それを提供する要素を呼び出すと、onclick
すべてのブラウザーですぐにハンドラーが呼び出されます(少なくともこれは一貫しています!)。
(on...
ここでは直接イベントハンドラーのプロパティを使用していますが、addEventListener
およびでも同じことが起こりattachEvent
ます。)
また、コードをスレッド化しているときに、それを誘発するために何もしなかったにもかかわらず、イベントが発生する可能性がある状況もたくさんあります。例:
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以外のすべてのブラウザーでは、いずれかのブラウザーがビジー状態のときにすべてのウィンドウ、タブ、フレームをブロックします。別のドキュメントのコードからドキュメントを操作し、別の実行スレッドで実行して、関連するイベントハンドラーに火。
スクリプトがまだスレッド化されている間に、発生させることができるイベントを発生させることができる場所:
モーダルポップアップは(ときalert
、confirm
、prompt
)すべてのブラウザが、オペラでは、開いています。
中にshowModalDialog
それをサポートするブラウザ上。
「このページのスクリプトはビジー状態です...」ダイアログボックスでは、スクリプトの実行を継続することを選択した場合でも、スクリプトの途中でもサイズ変更やぼかしなどのイベントを起動して処理できます。 Operaを除いてビジーループ。
しばらく前の私にとって、Sun Java Pluginを使用するIEでは、アプレットの任意のメソッドを呼び出すと、イベントが発生し、スクリプトに再び入ることができました。これは常にタイミングの影響を受けやすいバグであり、Sunがそれを修正した可能性があります(私は確かにそう望んでいます)。
おそらくもっと。私がこれをテストしてからしばらく経ちましたが、ブラウザは複雑になっています。
要約すると、JavaScriptはほとんどの場合、ほとんどの場合、厳密なイベント駆動型の単一の実行スレッドを持っているように見えます。実際にはそのようなことはありません。これがどれだけバグであり、どの程度意図的に設計されているかは明らかではありませんが、複雑なアプリケーション、特にクロスウィンドウ/フレームスクリプティングアプリケーションを作成している場合、それが断続的に噛み付く可能性があります。デバッグが難しい方法。
最悪の事態が発生した場合、すべてのイベント応答を間接化することにより、同時実行性の問題を解決できます。イベントが入ってくると、それをキューに入れ、後でsetInterval
関数内で順番にキューを処理します。複雑なアプリケーションで使用する予定のフレームワークを作成している場合は、これを行うとよいでしょう。postMessage
また、将来的にはクロスドキュメントスクリプティングの苦痛を和らげることが期待されます。
blur
後に IEが起動します。
ブラウザーのJavaScriptエンジンが非同期に実行すると、事実上すべての(少なくともすべての重要な)JavaScriptコードが機能しなくなるためです。
加えて、HTML5が基本的なJavaScriptにマルチスレッドを導入するWebワーカー(マルチスレッドJavaScriptコード用の明示的で標準化されたAPI)をすでに指定しているという事実はほとんど無意味です。
(他のコメント者へのメモ:にもかかわらずsetTimeout/setInterval
、HTTPリクエストのオンロードイベント(XHR)、およびUIイベント(クリック、フォーカスなど)は、マルチスレッドの大まかな印象を提供します-それらはすべて単一のタイムラインに沿って実行されます時間-したがって、事前にその実行順序がわからなくても、イベントハンドラー、時間指定関数、またはXHRコールバックの実行中に外部条件が変化することを心配する必要はありません。)
私はと言うでしょう仕様は防ぐことはできませんから、誰かをエンジンの作成走る上でJavaScriptを複数のスレッドで共有オブジェクトの状態にアクセスするための同期を実行するためのコードを必要とし、。
シングルスレッドの非ブロッキングパラダイムは、UIがブロックしてはならないブラウザーでJavaScriptを実行する必要性から生まれたと思います。
Nodejsはブラウザのアプローチに従いました。
ただし、Rhinoエンジンは、さまざまなスレッドでのjsコードの実行をサポートしています。実行はコンテキストを共有できませんが、スコープを共有できます。この特定のケースでは、ドキュメントには次のように記載されています。
...「Rhinoは、JavaScriptオブジェクトのプロパティへのアクセスがスレッド間でアトミックであることを保証しますが、同じスコープで同時に実行されるスクリプトを保証しません。2つのスクリプトが同時に同じスコープを使用する場合、スクリプトはシェア変数へのアクセスを調整する責任があります。」
Rhinoのドキュメントを読んだところ、誰かが新しいJavaScriptスレッドを生成するJavaScript APIを記述できる可能性があると結論付けましたが、APIはRhino固有です(たとえば、ノードは新しいプロセスしか生成できません)。
JavaScriptで複数のスレッドをサポートするエンジンであっても、マルチスレッドやブロッキングを考慮しないスクリプトとの互換性があるはずです。
Concearning ブラウザとnodejs方法私はそれがある参照してください。
そのため、ブラウザやnodejs(おそらく他の多くのエンジン)の場合、JavaScriptはマルチスレッド化されていませんが、エンジン自体はマルチスレッド化されています。
Webワーカーの存在は、別のスレッドで実行されるJavaScriptでコードを作成できるという意味で、JavaScriptをマルチスレッド化できることをさらに正当化します。
ただし、Webワーカーは、実行コンテキストを共有できる従来のスレッドの問題をカレーしません。上記のルール2と3も引き続き適用されますが、今回はスレッド化されたコードがJavaScriptでユーザー(jsコードライター)によって作成されます。
考慮すべき唯一のことは、効率性の観点から(並行性ではなく)生成されたスレッドの数です。下記参照:
Workerインターフェースは実際のOSレベルのスレッドを生成します。注意深いプログラマーは、注意しないと、並行性がコードに「興味深い」影響を与える可能性があることを懸念するかもしれません。
ただし、ウェブワーカーは他のスレッドとの通信ポイントを注意深く制御しているため、並行性の問題を引き起こすことは実際には非常に困難です。スレッドセーフでないコンポーネントやDOMにはアクセスできません。また、シリアル化されたオブジェクトを介して、スレッドとの間で特定のデータを渡す必要があります。そのため、コードに問題を引き起こすために、本当に一生懸命働かなければなりません。
理論に加えて、常に受け入れられた回答に記載されている可能性のあるコーナーケースとバグについて準備してください
JavaScript / ECMAScriptは、ホスト環境内で動作するように設計されています。つまり、ホスト環境が特定のスクリプトを解析して実行し、JavaScriptを実際に役立つようにする環境オブジェクト(ブラウザーのDOMなど)を提供しない限り、JavaScriptは実際には何も実行しません。
特定の関数またはスクリプトブロックは1行ずつ実行されると思いますが、これはJavaScriptで保証されています。ただし、おそらく、ホスト環境が同時に複数のスクリプトを実行する可能性があります。または、ホスト環境は常にマルチスレッドを提供するオブジェクトを提供できます。setTimeout
およびsetInterval
は、いくつかの並行性を実行する方法を提供するホスト環境の例、または少なくとも疑似例です(厳密に並行性でなくても)。
番号。
ここでは群衆に反対しますが、我慢してください。単一の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;
}
2番目のスレッド
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は伝統的にシングルスレッドであると言って間違いないと思います。
私は少し変更して@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>
したがって、[実行]を押してアラートポップアップを閉じ、「シングルスレッド」を実行すると、次のように表示されます。
click begin
click end
result = 20, should be 20
しかし、これをWindowsのOperaまたはFirefoxの安定版で実行し、画面にアラートポップアップを表示してウィンドウを最小化/最大化しようとすると、次のようなものになります。
click begin
resize
click end
result = 15, should be 20
これは「マルチスレッド化」であるとは言いたくありませんが、一部のコードが間違ったタイミングで実行され、これを予期していなかったため、破損した状態になりました。そして、この振る舞いについて知っておくと良いでしょう。
2つのsetTimeout関数を相互に入れ子にしてみてください。それらはマルチスレッドで動作します(つまり、外部タイマーは内部の関数が完了するのを待たずに、その関数を実行します)。
setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)
記録としては、常に最初に来る必要があり、その後コンソールログの出力になるはずです)