JavaScript Multi-threading
JavaScript strategies can use the threading object to create threads that really run in parallel, and exchange data between them with messages, shared dictionaries, locks and similar objects. This page explains when to use threads and how to organize thread code; for the parameters and return values of each function see Threads in the syntax manual.
Pick the right tool first
| Need | Recommended | Languages |
|---|---|---|
| Send several API requests at once (for example tickers from several exchanges) and wait for the results | exchange.Go, with EventLoop to wait for completion events | All languages |
| Long-running background work: separate market data collection, risk checks, heavy computation | threading.Thread | JavaScript only |
| Serve HTTP, WebSocket or TCP from inside the strategy | threading.Serve | JavaScript only |
When you only need a few concurrent requests, exchange.Go() is simpler and involves no data passing between threads. The threading object on this page is for JavaScript strategies only; Python and Rust strategies use exchange.Go().
These functions can be called in the backtesting system, but the threads actually run one after another there; this only keeps the code runnable in backtests.
Threads run in isolated environments
The function passed to threading.Thread() runs in a separate JavaScript environment. This is the most important thing to keep in mind when writing thread code:
- A thread function cannot reference outer variables or closures, nor call other functions defined in the strategy. Pass the data it needs as arguments:
threading.Thread(func, arg1, arg2, ...). - Plain objects and arrays passed as arguments are deep-copied: changing them inside the thread does not affect other threads. When several threads need to see the same data, use a dictionary created by
threading.Dict(). - Functions can be passed as arguments too;
threading.Thread()also accepts function source strings, which can be used to load external libraries in the thread. - Platform API functions such as
exchange.GetTicker()andLog()can be called directly in a thread. - The return value of the thread function is retrieved with
join():t.join().ret.
Exchanging data between threads
| Method | Usage | Notes |
|---|---|---|
| Messages | t.postMessage(msg) sends to thread t; inside a thread, threading.currentThread().peekMessage(timeout) reads the messages it received; a child thread sends back to the main thread with threading.mainThread().postMessage(msg) | Each thread has its own inbox, read in order. peekMessage(-1) does not block and returns an empty value when there is no message |
| Shared dictionary | var d = threading.Dict(), pass it to threads as an argument, then each thread uses d.get(key) and d.set(key, value) | Good for holding the "latest state", such as the latest price or a running flag |
| Thread data | t.setData(key, value), t.getData(key) | Key-value pairs attached to a thread object; invalid after the thread ends (join(), terminate()) |
| Synchronization objects | threading.Lock(), threading.Event(), threading.Condition() | Passed to threads as arguments for mutual exclusion and waiting for notifications |
A message received by a thread also raises an event, so the thread object's eventLoop can wait for messages and other events in one place.
Thread lifecycle
t.join()waits for the thread to end and returns its result, with an optional timeout;t.terminate()ends a thread forcibly.- When a thread has ended and is no longer referenced, its resources are reclaimed automatically; there is no need to call
join()just to free them. An error is raised when more than 2000 threads are kept referenced and cannot be reclaimed. threading.pending()returns the number of running threads (main thread included).- All threads end when the live trading stops. Waits in
peekMessage(),join(), locks and events are interrupted by the stop.
Serving from inside the strategy
threading.Serve(address, handler, ...args) starts an HTTP (WebSocket included) or TCP service inside the strategy process. Each request or connection calls the handler in its own thread. It returns a Server object (addr() gives the actual listening address, close() shuts it down). Like thread functions, handlers run in isolated environments and receive what they need as arguments; a threading.Dict() is commonly used to share state with the main thread. For the address syntax and the methods of the ctx object see Serve.
The old global function __Serve() still works but only returns the listening address string; use threading.Serve() in new code.
Examples
Examples
-
Several threads compute in parallel, the main thread collects the results
Each thread fetches the K-lines of one symbol and computes a moving average; the result goes back to the main thread as the return value. Note that the symbol is passed as an argument and the thread function references no outer variables.
javascriptfunction main() { var symbols = ["BTC_USDT", "ETH_USDT", "SOL_USDT"] var threads = [] for (var i = 0; i < symbols.length; i++) { threads.push(threading.Thread(function(symbol, period) { // runs in the thread: only arguments and platform APIs are available var records = exchange.GetRecords(symbol, period) if (!records || records.length < 20) { return null } var ma = TA.MA(records, 20) return {symbol: symbol, close: records[records.length - 1].Close, ma20: ma[ma.length - 1]} }, symbols[i], PERIOD_H1)) } for (var i = 0; i < threads.length; i++) { var r = threads[i].join().ret if (r) { Log(r.symbol, "close:", r.close, "MA20:", r.ma20) } } } -
A background thread collects prices, the main thread reads them and sends commands
The background thread writes the latest price into a shared dictionary and reports errors to the main thread through messages; the main thread tells it to exit with a message.
javascriptfunction main() { var shared = threading.Dict() var worker = threading.Thread(function(dict, symbol) { while (true) { // read commands from the main thread; -1 means do not block var cmd = threading.currentThread().peekMessage(-1) if (cmd == "stop") { break } var ticker = exchange.GetTicker(symbol) if (ticker) { dict.set("last", ticker.Last) dict.set("time", ticker.Time) } else { threading.mainThread().postMessage("failed to get ticker: " + GetLastError()) } Sleep(1000) } return "worker exited" }, shared, "BTC_USDT") for (var i = 0; i < 10; i++) { // wait at most 1 second for a message from the background thread var msg = threading.currentThread().peekMessage(1000) if (msg) { Log("background thread reports:", msg) } LogStatus("last:", shared.get("last"), "time:", _D(shared.get("time"))) } worker.postMessage("stop") Log(worker.join().ret) } -
A status endpoint with threading.Serve
The main thread writes the state into a shared dictionary; the HTTP handler gets the same dictionary as an argument and returns it as JSON.
javascriptfunction main() { var state = threading.Dict() var server = threading.Serve("http://127.0.0.1:8088", function(ctx, st) { if (ctx.path() == "/status") { ctx.setHeader("Content-Type", "application/json") ctx.write(JSON.stringify({last: st.get("last"), updated: st.get("updated")})) } else { ctx.setStatus(404) } }, state) Log("listening on:", server.addr()) while (true) { var ticker = exchange.GetTicker("BTC_USDT") if (ticker) { state.set("last", ticker.Last) state.set("updated", _D()) } Sleep(3000) } }
See Also