Visit Mozilla.org

SpiderMonkey Internals: Thread Safety

出典: MDC

This page describes implementation details of the SpiderMonkey JavaScript engine. It is mainly of interest to people working on SpiderMonkey itself, but this information is also helpful for anyone embedding SpiderMonkey in a multithreaded environment. See also JS_THREADSAFE.

目次

[編集] General background

SpiderMonkeyは、最上位の構造体としてJSRuntimeを利用します。これらの構造体は、メモリの管理とグローバルなデータ構造を扱います。 通常の場合プログラムは、多くのスレッドを利用する場合でも、1つだけJSRuntimeを使います。JSRuntimeは、JSオブジェクトが動作する 世界といってもよいでしょう。オブジェクトは、他のJSRuntimeに移って動作することはできません。

全てのJSコードとほとんどのJSAPIの呼び出しは、JSContextの中で動作します。JSContextは、 JSRuntimeの子供のようなもので、例えば、例外の処理などは、JSContextごとに実行されます。 各JSContextは、同時に複数スレッドからアクセスしてはなりません。

オブジェクトは、同じJSRuntime内のJSContext間で共有できます。コンテキストとオブジェクトの間には、固定的な関係はありません。

SpiderMonkeyにおけるスレッドセーフ機能は、-DJS_THREADSAFEをつけてコンパイルすることで有効になります。JS_THREADSAFEを有効にしたビルドでは、次のような操作について特別な処理が行われます。

  • JSRuntimeのデータ構造にアクセスする場合
  • ガーベジコレクションを行う場合
  • オブジェクトのプロパティにアクセスする場合

JSRuntimeのデータ構造へのアクセスは、mutexによってシリアライズされます。 GCとプロパティに関する処理については、もう少し詳しく説明します。


[編集] Making GC thread-safe

JS_THREADSAFEを用いるときは、APIを若干変更します。プログラム中でJSAPIを呼び出すときは、次に示す"request"で くくらなくてはなりません。

   JS_SetContextThread(cx);
   JS_BeginRequest(cx);
   /* ... do stuff ... */
   JS_EndRequest(cx);
   JS_ClearContextThread(cx);

複数のスレッドがrequest内で同じJSRuntimeを同時にアクセスできるため、この操作がボトルネックになることはありません。詳細はJS_BeginRequestを参照してください。

requestの最も顕著な効果は、いつでも複数のスレッドがrequestのくくりを実行することができるか、1つのスレッドだけがGCを実行していて他のスレッドが停止させられている 用にすることです。JS_GC()を呼び出しても、他のスレッドが停止させられるまでは、処理がブロックされます。つまり、他のスレッドがJSAPIを呼び出していないか (呼び出していないときには、特に注意する必要がないので)、JSAPIを実行中であってもGCが終了するのを待っているブロックしている状態になるまで、 GCの実行は停止させられます。

requestのくくりの中にいないときには、スレッドは、GCに影響を与えるような処理を行ってはいけません。 当たり前のことですが、requestのくくりの中では、GCが抑止されてしまうので、ブロックしたり、時間のかかる処理を行ってはいけません。


[編集] Making property accesses thread-safe

JSAPIのユーザにとっては、プロパティへのアクセスは全てシリアライズされているように見えます。これから記述する方法は、SpiderMonkeyの内部に関するもので ユーザにとっては見えない、最適化についてです。

SpiderMonkeyの実装では、mutableなオブジェクトは必要に応じて暗黙のうちにロックされます。ロックの手順は、うまく最適化されていて、単なるmutexではありません。

それぞれの、mutableなオブジェクトは、JSContextが"占有"している(つまり、コンテキストがロックをしなくてもプロパティにアクセスできる) か、JSRuntime内のJSContextで、"共有"しているかのいずれかです。(繰り返しになりますが、JSAPIのユーザにとっては、全てのオブジェクトは 共有されており、この"所有関係"は、ユーザには見えないように最適化されています。)

初期状態では、オブジェクトの所有者は、オブジェクトを作ったJSContext</code.です。他の<code>JSContextがオブジェクトにアクセスしようとするまでは、 ロックは全く必要ではありません。他のコンテキストがアクセスしようとした時点で、JSRuntimeの広域ロックを取得します。ただ、この時点においても プロパティへの通常のアクセスは、1つのオブジェクト(つまり、プロパティを持っているオブジェクトのこと)のmutable部分に触る必要があるだけです。 従って、デッドロックは問題になりません。[*] また、スレッドがロックする必要がある場合でも、オブジェクトを所有しているコンテキストが requestのくくりを実行していないのであれば、コストのかかる他のスレッドとのランデブ[**]を避けることができます。

I found it helpful to read the code for OBJ_GET_SLOT, defined in jsobj.h, and track down the various things it calls.

   /* Thread-safe functions and wrapper macros for accessing slots in obj. */
   #define OBJ_GET_SLOT(cx,obj,slot)                                     \
       (OBJ_CHECK_SLOT(obj, slot),                                       \
        (OBJ_IS_NATIVE(obj) && OBJ_SCOPE(obj)->ownercx == cx)            \
        ? LOCKED_OBJ_GET_SLOT(obj, slot)                                 \
        : js_GetSlotThreadSafe(cx, obj, slot))

Here OBJ_CHECK_SLOT() is just an assertion. LOCKED_OBJ_GET_SLOT() is the fast path; it amounts to an array access. OBJ_SCOPE(obj)->ownercx is the object's owning context, or NULL if the object is "shared". (An OBJ_SCOPE is just a handy place to stick this field; it is often shared across multiple objects, so all this locking is somewhat higher than object-level.)

This may appear unsafe, at least in SMP environments where writing a word isn't guaranteed to make the new value immediately visible to other CPUs. Requests save the day again: entering or leaving a request always briefly acquires a lock, which forces a read-write barrier. This barrier is necessary and sufficient to make several of these optimizations safe.

In short, any JSContext may touch any object, yet not only is locking usually optimized away, the threads don't even have to execute atomic instructions or barrier instructions in the most common path.


[*] deadlock isn't an issue: That is, SpiderMonkey doesn't need any special code to detect and avoid potential deadlock when getting or setting an ordinary property, because it can't happen--you're only locking one object at a time. Assigning to __proto__ is an example of a special case: SpiderMonkey checks for prototype chain cycles, which means locking the whole chain. So in that case, and maybe others, SpiderMonkey does extra work to avoid deadlock.

[**] can still avoid a costly rendezvous: That is, it can avoid "asking" that thread to surrender the object and then waiting for the thread to respond. It just takes the object. See ClaimScope in jslock.c.


[編集] Patent

The SpiderMonkey request model is patented: http://www.wipo.int/pctdb/en/wo.jsp?wo=2003042845

The Mozilla Public License in the SpiderMonkey source code grants a worldwide royalty-free license to this invention.