Hello, everybody!
The script language 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
- 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.
- First motivation ... The post of introduction
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.
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 isthis
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.
- Therefore, when controlling the end, synchronize by using a method such as data transfer described later, and correctly
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.
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
.
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 byIsolate.receive(name)
many times.
- If you do not clear the buffer by
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.
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.
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!'.
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.
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()
.
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.
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.
Top comments (0)