I had to resort to having a pool of JS runtimes from which each thread would borrow a runtime when it wanted to execute code, and return it when it was finished. This got messy quickly when I found out that script objects can't be shared, and I needed to load an individual copy of each script for each runtime.
Furthermore, JS objects weren't able to be shared between threads even when I didn't have separate runtimes, and I needed to (this was actually the recommended approach) write them to a binary format using JS_WriteStructuredClone when one thread was finished with them, and reassemble them with JS_ReadStructuredClone on the other end.
In the end, I gave up trying to do multithreaded Javascript and went for a nice, single threaded event driven model. I don't regret it. Heck, since it's lock-free - and structured clone free - it's probably more scalable!