Kinx Library - Isolate (Multi Thread without GIL)
Tags: kinx, ruby, javascript, thread
Published: 2020-07-29 21:49:18

Hello, everybody!

The script language [Kinx](https://github.com/Kray-G/kinx) is published with the concept of **Looks like JavaScript, Feels like Ruby, Stable like AC/DC(?)**.

This time it is an Isolate, which is a native thread library without **GIL**.

* Reference
    * First motivation ... [The post of introduction](https://dev.to/krayg/kinx-as-a-script-language-4iko)
        * Kinx, I wanted a scripting language with a syntax of C family.
    * Repository ... https://github.com/Kray-G/kinx
        * I am waiting for pull requests.

As you can see from the name Isolate, as a thread model, each thread operates independently and does not share the memory. This is chosen to increase safety.

## Isolate

### Model of multi threading

The memory sharing model of threads like C/C++ is too dangerous and difficult. You have to pay attention much to the race condition trap and ensure deadlock control, but you will still get deadlock easily. The battle between multithreading and safety is still ongoing.

The threading model of Ruby and Python is safe, but its weakness is that GIL (Global Interpreter Lock) gives many limitations for parallelism.

* [GIL (Global Interpreter Lock)](https://en.wikipedia.org/wiki/Global_interpreter_lock)

Let's challenge this wall also at **Kinx**. Ruby hasn't been freed from GIL due to past concerns, but it's likely to move on to the next stage.

So Kinx prepared the mechanism named as **Isolate**. It's a completely independent **native thread**. The exchange of information is limited to Integer, Double and String. Therefore, if you want to send an object, you need to prepare the mechanism of serializing and deserializing it. The most easy way is to make it a string and just execute it because the source code of Isolate is given as a string.

But note that only the compile phase is not reentrant. So the compilation phase will be locked and processed in sequence.

### Isolate Object

Roughly, the Isolate object is used as follows.

* Create an Isolate object by `new Isolate(src)`. Not executed yet at this point. `src` is just a string.
* Compile & execute with `Isolate#run()`. The return value is `this` of the Isolate object.
* By `Isolate#join()`, wait for the thread to finish.
* When the main thread ends, all threads will end **with nothing to be cared**.
     * Therefore, when controlling the end, synchronize by using a method such as data transfer described later, and correctly `join` in the main thread.

### Example

#### Creating a new thread

Look at the example first. What is passed to the constructor of Isolate is just a **string**. It feels good to looks like a program code when write it as a raw string style, but there is a trap on it.

* A `%{...}` in a raw string has been recognized as an inner-expression for the raw string itself.

So, you had better avoid to use `%{...}` inside a raw string. 
For example below, using `%1%` for that purpose and applying the value directly into a string. It is like a little JIT.

```javascript
var fibcode = %{
    function fib(n) {
        return n < 3 ? n : fib(n-2) + fib(n-1);
    }
    v = fib(%1%);
    var mutex = new Isolate.Mutex();
    mutex.lock(&() => System.println("fib(%1%) = ", v));
};

34.downto(1, &(i, index) => new Isolate(fibcode % i).run())
    .each(&(thread, i) => { thread.join(); });
```

Locking for printing has been used to avoid to a strange output.

```
fib(15) = 987
fib(10) = 89
fib(20) = 10946
fib(3) = 3
fib(11) = 144
fib(21) = 17711
fib(4) = 5
fib(9) = 55
fib(23) = 46368
fib(16) = 1597
fib(14) = 610
fib(8) = 34
fib(2) = 2
fib(24) = 75025
fib(26) = 196418
fib(28) = 514229
fib(29) = 832040
fib(7) = 21
fib(30) = 1346269
fib(25) = 121393
fib(5) = 8
fib(13) = 377
fib(12) = 233
fib(19) = 6765
fib(22) = 28657
fib(18) = 4181
fib(17) = 2584
fib(6) = 13
fib(27) = 317811
fib(31) = 2178309
fib(1) = 1
fib(32) = 3524578
fib(33) = 5702887
fib(34) = 9227465
```

The order might be changed because of a multi thread.

```
fib(10) = 89
fib(19) = 6765
fib(14) = 610
fib(11) = 144
fib(26) = 196418
fib(17) = 2584
fib(21) = 17711
fib(20) = 10946
fib(9) = 55
fib(13) = 377
fib(28) = 514229
fib(18) = 4181
fib(30) = 1346269
fib(31) = 2178309
fib(7) = 21
fib(3) = 3
fib(8) = 34
fib(4) = 5
fib(25) = 121393
fib(16) = 1597
fib(22) = 28657
fib(23) = 46368
fib(12) = 233
fib(27) = 317811
fib(29) = 832040
fib(15) = 987
fib(2) = 2
fib(5) = 8
fib(1) = 1
fib(6) = 13
fib(32) = 3524578
fib(24) = 75025
fib(33) = 5702887
fib(34) = 9227465
```

#### End of thread

The thread will be finished when the `Isolate` code has been reached at the end.
The returned status code from the thread will be returned as a return code of `join`.

```javascript
var r = new Isolate(%{ return 100; }).run().join();
System.println("r = %d" % r);
```

```
r = 100
```

#### Transfer data - Isolate.send/receive/clear

For simple data transfer you can use `Isolate.send(name, data)` and `Isolate.receive(name)`. The buffer is distingished by `name`, the threads are send/receive data by the `name`.

* `name` can be omitted. When it is omitted, it is same as specifying `"_main"`.
* As `data`, only Integer, Double, and String is supported.
	* That is why for an object, you should stringify it and it should be reconstructed by receiver.
* To clear the buffer by `Isolate.clear(name)`.
    * If you do not clear the buffer by `Isolate.clear(name)`, the buffer data will be remaining. It means the same data can be got by `Isolate.receive(name)` many times.

#### Mutex

Mutex object is constructed by `Isolate.Mutex`. By the way, the mutex is distinguished by the name even if it is in the same process.

```javascript
var m = new Isolate.Mutex('mtx');
```

By using the same name, the same mutex will be constructed. If you omit the name, the name will be the same as `"_main"`.

Mutex object is used with `Mutex#lock(func)` method. The callback function of `func` is called with a locked mutex.

```javascript
var m = new Isolate.Mutex('mtx');
m.lock(&() => {
    // locked
    ...
});
```

#### Condition

You can use a condition variable. That is used with a mutex object together. When passing a locked mutex to `Condition#wait()`, it waits after mutex is unlocked. In that status, when another thread do the `Condition#notifyAll()` and the thread can get the lock, to come back from the waiting status. 

`Condition#notifyOne()` is not supported because everybody says 'nobody should use it!'. 

```javascript
var cond = %{
    var m = new Isolate.Mutex('mtx');
    var c = new Isolate.Condition('cond');
    m.lock(&() => {
        var i = 0;
        while (i < 10) {
            System.println("Wait %1%");
            c.wait(m);
            System.println("Received %1%");
            ++i;
        }
        System.println("Ended %1%");
    });
};

var ths = 34.downto(1, &(i, index) => new Isolate(cond % i).run());
System.sleep(1000);
var c = new Isolate.Condition('cond');
16.times(&(i) => {
    System.println("\nNotify ", i);
    c.notifyAll();
    System.sleep(500);
});
ths.each(&(thread) => {
    thread.join();
});
```

#### NamedMutex

It is a mutex object with using between processes. To construct it, use `Isolate.NamedMutex`, but the usage is same as a normal mutex object.

But I do not know if it is good that the name should be `Isolate.NamedMutex`, because it role is over the `Isolate`. If you have any idea of that, please let me know. For example, `Process.NamedMutex`, or `System.NamedMutex`, or something.

```javascript
var mtx = Isolate.NamedMutex('ApplicationX');
mtx.lock(&() => {
   ...
});
```

It is used when you want to exclusive it with other processes.

### Data serialization and deserialization

So far, there is no feature of serializing and deserializing data. You do it yuorself. In fact, I hope I want to add some features for that, so I am now thinking about the functionality of that.

Now what you can do is to stringify it and reconstruct it to the object. When it is the JSON object as a simple structure, you can realize it by `JSON.stringify` and `JSON.parse`. As an another simple way, you can also put it directly with `toJsonString()`.

```javascript
var t = %{
    var o = %1%;
    System.println(["Name = ", o.name, ", age = ", o.age].join(''));
};
var o = {
    name: "John",
    age: 29,
};
new Isolate(t % o.toJsonString()).run().join();
```

```
Name = John, age = 29
```

You want to pass data dynamically, you need the code to deserialize it.

```javascript
var t = %{
    var o;
    do {
        o = Isolate.receive();
    } while (!o);
    System.println("Received message.");
    o = JSON.parse(o);
    System.println(["Name = ", o.name, ", age = ", o.age].join(''));
};
var o = {
    name: "John",
    age: 29,
};
var th = new Isolate(t).run();
Isolate.send(o.toJsonString());
th.join();
```

```
Received message.
Name = John, age = 29
```

## Conclusion

To realize a native thread without GIL, I did a lot of things depended on the runtime context and I designed the C function of Kinx should be reentrant. I believe it is really unnecessary to lock by GIL, if there is no mistake and bug...

To tell the truth, I ca not promise I haven't made a mistake. I believe you understand it if you were a developper. But I don't face any problem so far. Of course I will fix it if you report the bug.

Anyway, this is a challenge!

As I wanted to add the functionality of Isolate as a multi-threading for the multi-core, I did it. But it is still on the early stage. Challenge anything!

See you next time.