Categories
Uncategorized

[JavaScript] All-you-can-eat series of JavaScript simulates multithreading concurrency

Preamble

Recently, learning is a fiery topic, and I, but also want to be a kind of scholar, that is, to put JavaScript and multithreaded concurrency these two eight sticks, to hard together, also wrote a concurrent-thread-js library. Awkward, when I found out the irrational thing in it, that is, what exactly is the application scenario for this stuff, I found that I’ve finished writing the code.

 
 
 

⚠️ Note! In this article refers to the thread function is asynchronous with JS simulated “false thread,” not in the sense of a true multi-threading, please do not misunderstand ⚠️

 
 

github address

https://github.com/penghuwan/concurrent-thread.js

The purpose of this paper

In fact, the library is of little use, but in the process of writing, I have developed a new understanding of the use of Promise, Async functions and event flow, and I am also learning and understanding how to write a non-business, generic npm module from scratch, so I want to share it with you. The real purpose of the text.

 

Okay, let’s start with a story.

scene one

Scene II

 

github address

https://github.com/penghuwan/concurrent-thread.js​github.com

Attention! Without considering webworker such a solution, we generally assume that JS is single-threaded.

concurrent-thread-js Features

Implement concurrency coordination functions, semantic, naming and role for single-threaded JavaScript referencing Java implementation, mention APIs such as sleep/join/interupt and lock and conditional variables, and provide functionality for inter-thread communication, rely on ES6 syntax, based on Promise and Async function implementation, therefore require Babel compilation to run. JavaScrPt is inherently single-threaded, so this is only a simulation at the API level. In the introduction below, each so-called thread is actually a normal asynchronous function, and on this basis implements the coordination of different threads.

 

Why not choose webworker achieve?

Yes, in general JS mock multithreading we may choose webworker, but it must require you to manually create additional webworker script files and use them through new work (‘work.js’), which does not achieve the API I want in my project, and note: the environment in webwork is not window! A lot of methods you can’t call. You can only take this kind of scheme, i.e. doing the function in the main thread, which is another reason I didn’t choose webworker.

Say this to say, but in fact most of the time or use webworker enough

 

When to use concurrent-thread-js

The problem is really the soul of torture, but since the code is written is written, and how I have to make up a reason to come out ah! amount. . . Let me see Ha

What it does is: you can use this concurrency mock library when JS projects need to let two functions not interfere with each other’s execution, and don’t want them to block the main thread, at the same time, but also want the two functions to achieve a similar requirement of coordination between concurrent multithreads…. Does this Nima have this application scenario?! (KIND).

 

API Overview

  • submit (function, [namespace]): receives a function, normal function or Async function, and executes “thread” asynchronously

  • sleep (ms): “Thread” sleep, sleep time can be specified MS, in milliseconds

  • join (ThreadName): “Thread” synchronization, the “Thread” function that calls this method will continue to execute after the ThreadName execution ends

  • interrupt (thread Name): “thread” interrupted affect “thread” internal call this.isInterrupted () return value

  • Lock.lock: lock, a time only one “thread” function to enter the critical section, other “threads” function to wait, fair and non-locking, that lined up behind the thread function does not have, in a random manner competition.

  • Lock.unlock: lift the unfair lock

  • Condition.wait: No execution condition, “thread” enters the waiting state and waits to be waken

  • Condition.notify: random wake of a wait “threads”

  • Condition.notifyAll: not written, wake up all the wait “threads”

  • getState: not finished acquiring “Thread” state, including RUNNALE (run), WAITING (waiting), BLOCKED (blocking), TERMINATED (termination)

Three classes: ThreadPool, Lock and Condition

Our API is written into three classes, respectively

  • ThreadPool class: Contains SubMb/Sleep/Join/Interrupt/getState method

  • Lock Class: Contains Lock.lock and Lock.unlock methods

  • Condition class: and Condition.notify method comprising Condition.wait

Note: The following “thread” refers to the asynchronous function that is simulated in JS

A1.submit method

submit an analog submitted to the thread pool thread

//

Note: For a step-by-step introduction, the following is a simplified code

//

The state of each thread function of storage, such as whether to interrupt and thread status

const threadMap = {}; class ThreadPool { //

Simulating Thread Interruption

interrupt(threadName) { } //

Analog Thread Synchronization

join(threadName, targetThread) { } //

Analog thread sleep

sleep(ms) { } }; function submit(func, name) { if (!func instanceof Function) return; //

Mode 1: pass in a named function; Mode 2: pass in the second parameter, the thread namespace

const threadName = func.name || name; //

ThreadMap is responsible for storing thread state data

threadMap[threadName] = { state: RUNNABLE, isInterrupted: false }; //

Let func asynchronous calls, while incoming function prototype scope bindings for ThreadPool

Promise.resolve({ then: func.bind(ThreadPool.prototype); }) }

 

First of all, we have done three things:

  1. Get the namespace of the thread function, and initialize the thread initial data state stored by the different threads global threadMap

  2. Take the submitted function func as the then argument of a thenable object in the Promise.Resolve method, which is equivalent to immediately “complete” a Promise, while executing func in the then method, func will execute asynchronously rather than synchronously, you can simply understand that it is similar to the execution of SetTimeout (func,0);

  3. The func scope bindings for ThreadPool instance of the new generation, ThreadPool defined above us to introduce our methods, such as sleep / join / interupt, etc., what good is it? This means that we can directly in the function by way of calling this.interrupt to call our API definition, and in line with our habits (note, in addition to ordinary function outside the class defined function arrow actually stored in the prototype )

submit(async function example() {
    this.interrupt();
});

 

But the problem is: now because all functions are called by this method in the ThreadPool prototype, we need to pass in the “thread” identity in the asynchronous function, such as the thread name. This is obviously inconvenient and not elegant, for example the following thread function named example

submit(async function example() {
    this.interrupt('example');
});

 

With this module users will be surprised: I obviously function in the example, why the name parameter example to call the method passed? ? Can not this thing inside the module did it?

Correct! Here is what we do this thing, we write a delegateThreadPool method, by its function name for the ThreadPool agency to handle different “threads” function

//

Returns ThreadPool after Agent

function delegateThreadPool(threadName) { //

threadName is pending thread name, incoming call when the submit method

//

ThreadPool after the proxy

const proxyClass = {}; //

Get all the original methods of ThreadPool, assign the props array

var props = Object.getOwnPropertyNames(ThreadPool.prototype); for (let prop of props) { //

Acting ThreadPool, threadName increase this parameter for all methods

let fnName = prop; proxyClass[fnName] = (...args) => { const fn = baseClass[fnName]; return fn(threadName, ...args); }; } return proxyClass; } function submit(func, name) { //

omit other code...

const proxyScope = delegateThreadPool(threadName); //

Let func asynchronous call, do not block the main thread, while achieving concurrency

Promise.resolve({ then: function () { //

Func for the ThreadPool to bind this subject after the proxy to invoke methods

func.call(proxyScope); } }); } //

When you call this.sleep method, has been named as a parameter function without increasing the

submit(async function example() { this.interrupt(); });

 

In other words, our thread function func is not bound ThreadPool.prototype, but rather the object returned after delegateThreadPool treatment: proxyScope. At this time, we in the “thread” in the body of the function is invoked this.interrupt method, has been named as a parameter without increasing function, because this work, proxyScope objects help us to do, in fact, it worked quite simple – it’s every functions, in a return to the closure of the same name which calls ThreadPool function, passing the thread name as the first parameter.

A2.Sleep method

Role: Thread hibernation

sleep is very simple, nothing more than a return Promise instance, in tune setTimeOut Promise function inside, so time to resolve the function execution, during which time the modified Promise await sentence will block for some time, resolve and then await the statement went down execution, and we want to meet the effects of sleep

//

Analog "thread" sleep

sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }) } //

Submit “Thread”

submit(async function example() { //

Obstruction stay three seconds before output 1

await this.sleep(3000); console.log(1); });

 

A3. interrupt method

Role: thread interrupts can be used to stop processing thread and other operations

Let’s introduce the interrupt method in Java: In Java, you can’t stop a thread by calling terminate method, because this may cause data inconsistency problems due to sudden interruptions in the processing logic, so use the interrupt method to position an interrupt flag as true, and then pass the The iInterrupted method breaks out of key code as a judgment condition.

So for mock, I do the same for dealing with “thread” breaks in JS, but the root reason we do this is: we don’t have a way to stop a thread function at all! (JAVA has but is not allowed to use, that is, it is only deprecated)

    //

Simulating Thread Interruption

interrupt(threadName) { if (!threadName) { throw new Error('Miss function parameters') } if (threadMap[threadName]) { threadMap[threadName].isInterrupted = true; } } //

Get thread interrupt status

isInterrupted(threadName) { if (!threadName) { throw new Error('Miss function parameters') } //

!!! is: turn undefined to false

return !!threadMap[threadName].isInterrupted; }

 

A4. join method

join (ThreadName): “Thread” synchronization, the “Thread” function that calls this method will continue to execute after the ThreadName execution ends

The join method is the same as the sleep method above. We let it return a promise. As long as we don’t change resolve, then the await statement that is externally decorating the promise will be suspended until the other thread of join is executed. Let’s see the timing! Give the promise to resolve, at which point the external modification of the await statement can be executed down again?

 
 

But the question is: How do we implement “feature complete implementation of a function to another function notice it,” this? Yes! That is our JavaScript favorite routines: the event flow! Here we use the generic event-emitter module to achieve this end before and after the event flow.

We only triggered when any function ends the end of the event (join-finished), while passing the function name of the thread as a parameter, and then listen for the event in the internal join method, and response time to call resolve method is not to be proud.

 

The first is listening for the end event of the thread function inside the join method

import ee from 'event-emitter';
const emitter = ee();
//

Analog Thread Synchronization

join(threadName, targetThread) { return new Promise((resolve) => { //

Listening for end events for other thread functions

emitter.on('join-finished', (finishThread) => { //

According to the end of the thread thread name to make a judgment finishThread

if (finishThread === targetThread) { resolve(); } }) }) }

 

Also trigger join-finished event at the end of thread function execution, passing thread name as argument

import ee from 'event-emitter';
const emitter = ee();
function submit(func, name) {
   // ...
    Promise.resolve({
        then: func().then(() => {
          emitter.emit('join-finished', threadName);
        })
    });
}

Use as follows:

submit(async function thread1 () {
  this.join('thread2');
  console.log(1);
});
submit(async function thread2 () {
  this.sleep(3000);
  console.log(2)
})
//

After 3s, output 2 1 in turn

A5. Lock.lock & Lock.unlock (unfair lock)

We mainly want to write two methods: lock and unlock methods. We need to set a Boolean property isLock

  • lock method: lock method will first determine whether isLock is false, and if so, on behalf of occupation no thread critical section, the thread is allowed to enter the critical section, while the isLock set to true, does not allow other threads to enter the function. When other threads to enter, because the judge isLock true, the setTimeOut recursive call from time to time determine whether isLock is false, so consumed by way of a lower performance simulation while an endless loop. When they detect isLock is false when it will enter the critical section, while isLock set to true. Because the thread behind in no particular order, so this is a fair and non-lock

  • Unlock method: Unlock is set the isLock property to false. Unlock it.

//

This is an unfair lock

class Lock { constructor() { this.isLock = false; } //

Lock

lock() { if (this.isLock) { const self = this; //

Loop while loop, constantly testing isLock is equal to false

return new Promise((resolve) => { (function recursion() { if (!self.isLock) { //

Holding the lock

self.isLock = true; //

External await the statement continues down

resolve(); return; } setTimeout(recursion, 100); })(); }); } else { this.isLock = true; return Promise.resolve(); } } //

Unlock

unLock() { this.isLock = false; } } const lockObj = new Lock(); export default lockObj;

 

The run example is as follows:

async function commonCode() {
    await Lock.lock();
    await Executor.sleep(3000);
    Lock.unLock();
}

submit(async function example1() {
    console.log('example1 start')
    await commonCode();
    console.log('example1 end')
});
submit(async function example2() {
    console.log('example2 start')
    await commonCode();
    console.log('example2 end')
});

 

Export

//

Output immediately

example1 start example2 start //

Output after 3 seconds

example1 end //

Three more seconds to output

example2 end

 

A6. Condition.wait & Condition.Notify (conditional variable)

  • Condition.wait: does have the condition, the thread enters the waiting state, waiting to be awakened

  • Condition.Notify: Wake Thread

Sorry! To write here, I really have a dry mouth, I can not write anymore, but the truth is the same as before:

Nothing more than: Event listening+Promise+Async function combo punch, set to get

import ee from 'event-emitter';
const ev = ee();

class Condition {
    constructor() {
        this.n = 0;
        this.list = [];
    }
    //

When the conditions are not satisfied, so that the thread is in a wait state

wait() { return new Promise((resolve) => { const eventName = `notify-${this.n}`; this.n++; const list = this.list; list.push(eventName); ev.on(eventName, () => { //

Remove an Event Name from a List

const i = list.indexOf(eventName); list.splice(i, 1); //

Getting the external function back to execution

debugger; resolve(); }) }) } //

Select a thread to wake up

notify() { const list = this.list; let i = Math.random() * (this.list.length - 1); i = Math.floor(i); ev.emit(list[i]) } }

 

Test code

async function testCode() {
    console.log('i will be wait');
    if (true) {
        await Condition.wait();
    };
    console.log('i was notified ');
}

submit(async function example() {
    testCode();
    setTimeout(() => {
        Condition.notify();
    }, 3000);
});

Export

i will be wait
//

Output after 3 seconds

i was notified

 

The last large sum

Actually, in the end, what I want to share with you is not a concurrency ah, what a lot of threads.

In fact, I want to say is: an event listener + Promise + Async function well with this combination of boxing ah

  • You want a piece of code stop by? OK! Promise to write a return of function, modified by await, it stopped it!

  • You want to control it (await) do not stop, and continue to go down? OK! Promise to resolve the fall, it’s gone down

  • You say you do not know how to control it to stop, because the code transmitter and monitor the distribution of events in two places? OK! Then use the event stream

 

This article finished, the following is the entire project code (just write an article only to find there are bug, will be changed to change)

Leave a Reply