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

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

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

1回答

1974閲覧

SpiderMonkeyでsetIntervalの実装方法

twinbird

総合スコア12

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

3クリップ

投稿2016/02/02 04:18

編集2022/01/12 10:55

Mozilla製のJavaScriptエンジンであるSpiderMonkey(v38)をC++アプリケーションに組み込むことを検討しています。
通常のブラウザで実装されているsetIntervalの実装方法がわからずに困っています。

setIntervalを実装するには、setIntervalに渡されたクロージャを別スレッドで実行する必要があると思い、
JSRuntimeを生成したスレッドではないスレッドで、実行したところ失敗しました。

JSRuntimeは、シングルスレッドでしか動かない仕様のようですね。
JSRuntimeを生成したスレッドではない別のスレッドで、JS_CallFunction等を呼び出すと、内部でスレッドIDの検査が行われて、実行できなようようになっているとどこかに書いてありました。

実際に試してみたら、クラッシュしました。

マルチスレッド環境で動かすには、スレッドの数だけJSRuntimeが必要なようですが、
そうするとJSRuntime同士は、メモリを共有していないので、setIntervalに渡すクロージャの外で定義したオブジェクトは、使えなくなるのではないでしょうか。

特にマルチスレッドにこだわっているわけではありません。
ブラウザ上では、シングルスレッド上で動いているようですし、シングルスレッドでの実装方法でも構いません。

setIntervalの実装方法をご教授願います。

ちなみに、以下が1つのJSRuntimeのみを用いてマルチスレッドで動かして失敗した例です。

C++

1/* 2SpiderMonkeyで同一のJSRuntimeを別スレッドで使用して、エラーになる例。 3*/ 4 5#include <iostream> 6#include <thread> 7#include <jsapi.h> 8 9using namespace std; 10 11/** 12 * 実行するJavaScript。 13 * spawn関数は、引数のクロージャを別のスレッドで評価する。 14 * ここでは、コンソールに'hello'と出力する。 15 * spawn、print関数は、下記でglobalオブジェクトに定義してある。 16 * 下記のmain関数内のJS::Evaluateでこのスクリプトを評価している。 17 */ 18static const char* script = "spawn(() => print(`hello\n`));"; 19 20/** 21 * spawnのC++での実装。 22 * 別スレッドでJS_CallFunctionを実行している。 23 */ 24bool js_spawn(JSContext *cx, unsigned argc, JS::Value *vp) 25{ 26 JS::CallArgs args = CallArgsFromVp(argc, vp); 27 // 念の為、別スレッドで呼ばれてもいいように、GCされないようにする。 28 JS::PersistentRooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 29 // 念の為、別スレッドで呼ばれてもいいように、GCされないようにする。 30 JS::PersistentRooted<JSFunction*> func(cx, JS_ValueToFunction(cx, args[0])); 31 32 // スレッド生成。 33 auto th = std::thread( [cx, global, func]() { 34 JS::RootedValue rval(cx); 35 if( !JS_CallFunction(cx, global, func, JS::HandleValueArray::empty(), &rval) ) 36 cerr << "ERROR: failed to call function at js_spawn()" << endl; 37 }); 38 th.join(); 39 return true; 40} 41 42/** 43 * 文字列をコンソールに出力する。 44 */ 45bool js_print(JSContext *cx, unsigned argc, JS::Value *vp) 46{ 47 JS::CallArgs args = CallArgsFromVp(argc, vp); 48 char* s = JS_EncodeString(cx, args[0].toString()); 49 printf("%s", s); 50 JS_free(cx, s); 51 return true; 52} 53 54static const size_t heapMaxBytes = 16L * 1024 * 1024; 55static const size_t nurseryBytes = 4L * 1024 * 1024; 56static const size_t maxStackSize = 6L * 1024 * 1024; 57static const size_t stackChunkSize = 8192; 58 59int main( int argc, char** argv ) { 60 61 JS_Init(); 62 63 JSRuntime *rt = JS_NewRuntime(heapMaxBytes, nurseryBytes); 64 65 JS_SetNativeStackQuota(rt, maxStackSize); 66 67 JSContext *cx = JS_NewContext(rt, stackChunkSize); 68 69 { 70 JSAutoRequest ar(cx); 71 72 JSClass globalClass = { 73 "global", 74 JSCLASS_GLOBAL_FLAGS 75 }; 76 77 JS::RootedObject global(cx, JS_NewGlobalObject( cx, &globalClass, nullptr, JS::FireOnNewGlobalHook)); 78 79 JS::RootedValue rval(cx); 80 { 81 JSAutoCompartment ac(cx, global); 82 83 JSFunctionSpec globalFunctions[] = { 84 JS_FS("print", js_print, 1, 0), 85 JS_FS("spawn", js_spawn, 1, 0), 86 JS_FS_END 87 }; 88 89 JS_DefineFunctions(cx, global, globalFunctions); 90 91 JS::CompileOptions opts(cx); 92 opts.setLine(1); 93 94 JS::Evaluate(cx, global, opts, script, strlen(script), &rval); 95 } 96 97 } 98 99 JS_DestroyContext(cx); 100 JS_DestroyRuntime(rt); 101 JS_ShutDown(); 102}
mhashi👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

think49

2016/02/02 04:21 編集

私は C++ に詳しいわけではありませんが、現象を再現可能なコードを示さないと回答が寄せられにくいと思われます。
twinbird

2016/02/02 05:43

ご指摘ありがとうございます。 再現可能なコードを追加しました。 C++11とjsapiがあればコンパイルし、実行できると思います。
guest

回答1

0

spidermonkey と java rhino は源流は同じでも実装も違うのでさんこうになるかわかりまsせんが、"文字列"のメソッドならば、タイマーすれっどから呼び出すことができました。

c++実装と当方の実装のscript の相違点は、以下の通りでづ
spawn メソッドに、グローバルスコープのプレフィックスを付けた。よびだされる print も同様。
匿名メソッドの実行方法がふめいのため、文字列メソッドをevalした。

その他の違いはありません。コンテキストに、ctx.put("window", this)
として、クラス内を参照できるようにし、setTimeout, clearTimeout, print メソッドを public で実装。
もじれつのスクリプトは、
window.setTineout("window.print('ok')", 5000);
です。

window がブリッジですね。

-----/------/------
spidermonkey のチュートリアルのメソッド呼び出し方法の実装手順どおりにやった場合はせいこうしたんですか。ラムダ式や匿名メソッドが実行できると思った根拠はなんでしょうか。
家に帰ったらc++で実験してみます。
-----/-------/------
java はこのスレでは関係ないのでアレですが、匿名メソッドも普通に実行できるみたいですね。
spidermonkey 匿名メソッド 実行で一杯出てきました。

投稿2016/02/02 06:41

編集2016/02/02 07:36
ipadcaron

総合スコア1693

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

twinbird

2016/02/02 09:04

ご回答ありがとうございます。 SpiderMonkey(v38)は、ES6をほぼサポートしています。 なので、ラムダ式は使えます。 上述の私のC++コードでは、スレッドを使わなければ、正常に実行できます。 問題は、マルチスレッド環境下での実行です。 一応、スレッド内で文字列を評価して実行する方法も試してみましたが、うまく行きませんでした。 SpiderMonkeyのソースコードを見てみると、Runtime.cppなどで次のような記述がみられます。 rt->ownerThread_ == PR_GetCurrentThread(); これは、実行スレッドが、JSRuntimeのスレッドと同じかどうかを検査していると思われます。 1つのJSRuntimeは、マルチスレッド環境では、動かない仕様になっているようです。 ただ、Firefoxでは、どのようにsetIntervalを実装しているのか気になるので質問させていただきました。
ipadcaron

2016/02/02 09:51 編集

検索した限りでは、settimeout の how to が github にありましたよ。 スレッドとかタイマーとかじゃなくて、無限ループで日付生成して、、、やってる感じでした。 https://gist.github.com/kevinoid/3146420 これです。 spidermonkey hoe to implement settimuout で検索。 斜め読みだったので何やってるのか見てなかったんですが、スクリプトからc++ のthread.sleepのラッパーを呼び出してるんですね。ブロックさせないために、1ms 程度の浅い眠りで時間計り、到達したらコールバックを順番に呼び出すみたいな仕掛けですね。
twinbird

2016/02/02 12:53

ありがとうございます。 このコードは以前私も見ました。 ですが、この実装では、以下のように「timerLoop()」でブロックされて、すべてのコールバックが終了するまで、以後のコードが実行されない気がするのですが? var timerLoop = makeWindowTimer(this, function (ms) { sleep(ms / 1000); }); setInterval(() => { print('hello\n'); }, 100); timerLoop(); // ここでブロックされる print('this code will not be evaluated.'); // このコードは実行されない。
ipadcaron

2016/02/02 14:06 編集

基本的には、javascript は、現在実行中のインストラクションポインタがあって、タイマーは割り込みではなくて、現在処理の完了を待ってから実行されますよね。 javascript は割り込みや実行コードのプライオリティが無いので常にグルグル回る処理ループの中で逐次じっこう、即時実行タイプのメソッドなり処理などは、0秒タイマー指定による実行キューへの登録で持ってキュー自体は タイムシェアリングしてるので最低単位の1msのウェイトでもって、0秒指定のメソッドが実行されればリンクのサンプルでもイケる気がしてきました。確か 、windows のメッセージキューが付いたイベントループって、メッセージポンプを定期的に行う必要があるので極めて短い時間の sleep で時間区切りでうごいていたようなそうでないような、、、、 りんくのサンプルは、timerLoop中の処理単位それぞれのタイムアウト時間を毎回チェックする仕組みになっています。即時メソッド、例えばonclick など、は1ms のタイムロスでも命取りなので、c++のメインクラスに対して thread.interrupt して、sleep を解除、tinerLoopのタイムアウト判定処理を動かしてあげることで、onclick の0秒タイマーしか登録されていなかれば、最速で動作させることができます。 なんて空想抱きました。
twinbird

2016/02/02 23:16

確かに実際にブラウザでは、おっしゃるような実装と似た実装になってると思います。 すべては、キューに登録してそれを逐次実行するという形になっています。 例えば、 setInterval(() => console.log('hello')) while( true ) {} のようなコードは、setInterval内の処理は永遠に実行されせん。 永遠に「hello」と出力されません。 なぜなら、「while(true){}」が終わってから、つまりスクリプトが終わってから、キューの処理が実行されるからです。 実際のブラウザは、スクリプト単位でキューに登録していると思われるので、 スクリプトとスクリプトの間にsetIntervalなどの処理が実行されます。 ただ、私は、1つのスクリプトの処理内で非同期な処理を行いたかったのです。 この点において質問の仕方に問題があったかもしれません。 以前のバージョンのSpiderMonkeyでは、JS_SetContextThreadというメソッドがあり別のスレッドでも実行できたようですが、やはり今は、私が望んだことは無理なんでしょう。 お時間を割いていただきありがとうございました。
ipadcaron

2016/02/03 01:27

デバッガのぶれーくポイントの仕組みってご存知ですか。 実行中のステップに割り込みに似たような処理を「割り込ませる」のに、ブレークポイントのやり方が使えそうなきがします。。 他には、ずーっと前に本で読んだ記憶なので不確かか、時代錯誤かもしれませんが、スレッドのタスクスイッチの仕組みはご存知ですか。確か、intel系の場合は、near call 以上の呼び出しやジャンプ命令を契機にハードウェアでメモリマッピングのすり替えとインストラクションポインタの変更が行われる、とかそんな感じだったと思います。ハード依存な気もしますが、ソフトウェア割り込みとかもあるかもしれまsrん。スクリプト1本んp中で複数スレッド建てて同期取りつつ各自勝手に動作させる。ですか。int 21h とか、やるんじゃないかなぁ。 a=2;int;print("ok");int; みたいに、解析ツリーに「割り込み」用の穴を用意しておいて、変数はすべてatomic変数で、、、、なんかーへんだな。 ブラウザ組み込み用のspidermonkey は、入手可能なspidermonkey と全く同一ではないと思いますよ。js_なんちゃらめそっどは、cのプロトタイプの実装の他に、オーバーライド可能なクラスのメソッドとぢての実装はないのでしょうか。まぁ、どっちにしても、フリーのライブラリで商用きのうと同じ実装にしたければ、それなりにリスクのある選択肢を選びなさいってことなんででょうかね。タダでは同等機能は使わせませんよ、と。
twinbird

2016/02/03 22:55

確かに、ソフトウェア割り込みとかも1つの手ですね。 大変そうだけど。 あとは、SpiderMonkeyを自分で書き換えちゃうっていう手もありますね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問