JS_THREADSAFE 编辑
JS_THREADSAFE
was a compile-time option that enables support for running multiple threads of JavaScript code concurrently as long as no objects or strings are shared between them.
We have recently made major changes to this feature. Until recently, sharing objects among threads would mostly work, although scripts could easily make it crash. We have now completely removed that feature. Each thread that uses the JavaScript engine must essentially operate in a totally separate region of memory.
Requests
In a JS_THREADSAFE
build, the application must separate code that uses the JSAPI from code that performs blocking I/O or time-consuming calculations.
A request is a region of code that uses the JSAPI. Requests must be bracketed with calls to JS_BeginRequest()
and JS_EndRequest()
.
JS_BeginRequest(cx); /* ... do JSAPI stuff ... */ JS_EndRequest(cx);
A request is always associated with a specific JSContext
and runs from start to finish on a single thread.
Most JSAPI functions require the caller to be in a request. In this reference, these JSAPI functions are marked with the words "Requires request", like this:
Name | Type | Description |
---|---|---|
cx | JSContext * | The context to use. Requires request. In a JS_THREADSAFE build, the caller must be in a request on this JSContext . |
Most JSAPI callback functions are always called from within a request. These callbacks are (unreliably!) documented with the words "Provides request", like this:
Name | Type | Description |
---|---|---|
cx | JSContext * | The context in which the event ocurred. Provides request. In JS_THREADSAFE builds, the JavaScript engine calls this callback only from within an active request on cx . The callback does not need to call JS_BeginRequest() ). |
In particular, JSNative
callbacks provide a request. This means that any potentially long-running operation in a native must be bracketed with calls to JS_SuspendRequest()
and JS_ResumeRequest()
.
JSBool socket_recv(JSContext *cx, unsigned int argc, jsval *vp) { ... rc = JS_SuspendRequest(cx); read_size = recv(socket, buf, size, flags); JS_ResumeRequest(cx, rc); ... }
Garbage collection
Requests help make garbage collection safe when multiple threads are using the JSAPI. For each thread that is in a request:
- Almost any call into the JSAPI may trigger garbage collection; but
- Garbage collection does not happen at any other time (such as, for example, at the moment before the return value of
JS_NewObject
is assigned to a rooted variable).
These are actually the same rules that apply to single-threaded JSAPI programs. But in multithreaded programs, if you break the rules, your program is more likely to crash. This is because in single-threaded programs, a random call into the JSAPI is actually pretty unlikely to trigger GC, especially if the calling thread has not been using up a lot of memory. In a multithreaded program, even if the calling thread has been idle, other threads may be active or may call JS_GC()
.
The above rules mean that at any given moment, there can be either (a) multiple threads in active requests, or (b) one thread doing GC and all requests suspended. When one thread calls JS_GC or otherwise finds that garbage collection is necessary, it must wait for all other threads that are in requests to pause before garbage collection can occur. To keep this wait time to a minimum, applications must avoid long-running requests. The recommended technique in SpiderMonkey 1.8 and later is to periodically call JS_YieldRequest
from an operation callback.
Sharing data among threads
"Data can be marshaled across the process boundary through a process known as smuggling." --Mr. Bunny's Guide to ActiveX
Even in JS_THREADSAFE builds, threads cannot safely share objects or strings.
Instead, data must be copied when it is sent from one thread to another. Use JS_WriteStructuredClone
to transform data into a flat array of bytes that can be safely written to disk, sent to another process or even another machine, or just passed to another thread. Then use JS_ReadStructuredClone
on the other side to turn the serialized data back into JavaScript objects, strings, and so on.
Sharing native functions and private data among threads
In a JS_THREADSAFE
build, SpiderMonkey's internal data structures that represent JavaScript values are single-thread-only. In a DEBUG
build, this is enforced with assertions.
However, SpiderMonkey does not protect the application's data structures. JSNative
s and other callback functions can be called concurrently by multiple threads. Multiple threads can end up accessing private data or C/C++ global variables at the same time. It is up to the application to practice safe threading.
Sharing contexts among threads
Ordinarily, a JSContext
is created, used, and destroyed by a single thread. This makes sense, as a context can only be used by one thread at a time. However, there are a few cases where an application might need to share contexts across threads. For example:
- Many worker threads need to share a "pool" of reusable contexts, to avoid the performance cost of constantly creating and destroying contexts. (This is analogous to a database connection pool.)
- The application has a
JSContext
that it needs to use each time some event happens. But the event could happen on any thread.
For such cases, use JS_ClearContextThread
and JS_SetContextThread
to transfer the context safely from one thread to another.
Further info
Note: SpiderMonkey Internals: Thread Safety is mostly obsolete.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论