📜 ⬆️ ⬇️

Mash: multithreading, coroutines, async & wait

image

Foreword


Let me remind you that this language was developed by me for educational purposes as part of a hobby. I do not consider it (at the moment) to be an ideally worked language, but who knows what kind of future it can expect.

If you have a desire to try it in action yourself - download the project repository , in it you can find the assembled version of the project, or you can compile it yourself for your OS.
')

Introduction


Multithreading and asynchrony in our time are one of the most important components of modern programming languages.

Therefore, I decided to add to my programming language the support of modern constructions and technologies, partly by adding simple and convenient constructions to the language.

Fast streams


Allow to easily parallelize code execution.
For this purpose, the “launch: ... end” construction has been added to Mash.

Code example:

uses <bf> uses <crt> proc main(): for(i ?= 1; i <= 10; i++): launch: sleep(random() * 100) println(i) end end inputln() end 

Example output:

 9 1 2 7 5 3 10 4 6 8 

When the program reaches launch..end, the code inside this block is launched in a separate thread, and the execution of the launch code is transferred to this block.

You could meet almost the same language construct earlier in the Kotlin programming language.

Async & wait


Implementing korutin alone is not enough for me, that’s why async & wait constructs have been added to Mash.

Async allows you to translate code execution into a separate thread and continue with the main code.

Wait allows you to wait for the moment when all the necessary async blocks will be executed.

Code example:

 uses <bf> uses <crt> proc main(): println("Hello!") async a: println("Test") sleep(1000) println("Test") sleep(1000) println("Test") sleep(1000) end async b: println("Test 2") sleep(300) println("Test 2") sleep(300) println("Test 2") sleep(300) end wait a, b println("End!") inputln() end 

Conclusion:

 Hello! Test Test 2 Test 2 Test 2 Test Test End! 

Classic multithreading


The main code base that provides support for multithreading is concentrated in the? <Threads> module.

The main components that will be discussed further:

1) TThread class (only the class declaration is shown, the full code is located further in the module):

 class TThread: protected: var ThreadContext public: var Resumed, Terminated, FreeOnTerminate proc Create, Free proc Execute //for overriding proc Suspend, Resume, Terminate, WaitFor, ReJoin //Control proc's end 

2) TCriticalSection class (its description):

 class TCriticalSection: protected: var Critical_Section_Controller public: proc Create, Free //Methods proc Enter, Leave func TryEnter end 


3) Methods for quickly creating and running threads:
 func Async(method, ...) func Thread(method, ...) func Parallel(method, ...) 


4) Thread-safe atomic (class-variable for inter-thread interaction):
 class TAtomic: private: var Locker, Value public: proc Create, Free proc Set func Get end 


5) Coroutines:
 class TCoroutine(TThread): public: var NextCoroutine proc Create proc Yield, YieldFor end 


So, let's sort everything in order.

The TThread class allows us to create a new descendant class based on it, adding the necessary variables to its fields, which will be passed to the new thread.

Immediately sample code:

 uses <bf> uses <crt> uses <threads> class MyThreadClass(TThread): var Param proc Create, Execute end proc MyThreadClass::Create(Param): $Param ?= Param TThread::Create$(true) end proc MyThreadClass::Execute(): for(i ?= 0; i < 10; i++): PrintLn(i, ": ", $Param) end end proc main(): new MyThreadClass("Thread #2!") InputLn() end 

If we are too lazy to describe a new class, to create a stream, then we can recall the support for dynamic redefinition of methods on class instances and use it.

Code example:

 uses <bf> uses <crt> uses <threads> proc class::MyThreadedProc(): for(i ?= 0; i < 10; i++): PrintLn(i, ": Threaded hello!") end end proc main(): Thr ?= new TThread(false) Thr->Execute ?= class::MyThreadedProc Thr->Resume() InputLn() end 

If we just need to run the method with the parameters in the new thread, then the async (), thread () and parallel () methods are just what we need.

An example of running a method in a new thread:

 uses <bf> uses <crt> uses <threads> proc ThreadedProc(Arg): for(i ?= 0; i < 10; i++): PrintLn(i, ": ", Arg) end end proc main(): Async(ThreadedProc, "Thread #1!") InputLn() end 

As you may have noticed, these 3 methods are functions and return them - similar to TThread classes.

Their difference lies in the fact that async () creates a thread that, upon completion, will free up memory from itself, and an instance of the TThread class will be automatically deleted
thread () is the same as async (), only the thread is created initially frozen.
And finally, parallel () creates a running stream that, upon completion, does not perform self-destruction, i.e. we can use any methods of the TThread class, for example, WaitFor (), and not be afraid of the occurrence of runtime errors. The only caveat - you will need to call Free () manually.

Thread synchronization


For this, I added the TCriticalSection class to Mash.

Code example:

 uses <bf> uses <crt> uses <threads> var CSect = new TCriticalSection() proc ThreadedProc(Arg): while true: CSect -> Enter() PrintLn(Arg) CSect -> Leave() Sleep(10) gc() end end proc CriticalThreadedProc(): while true: Sleep(3000) CSect -> Enter() Sleep(1000) PrintLn("And now...") Sleep(1000) PrintLn("Time to...") Sleep(1000) PrintLn("Critical section!") Sleep(3000) CSect -> Leave() gc() end end proc main(): Async(ThreadedProc, "I'm thread #1!!!") Async(CriticalThreadedProc) InputLn() end 


Atomic


The implementation of a stream-safe container for storing any values.

Code example:
 uses <bf> uses <crt> uses <threads> proc main(): MyThreadValue ?= new TAtomic(0) launch: while true: MyThreadValue -> Set(1) Sleep(8) gc() end end launch: while true: MyThreadValue -> Set(2) Sleep(3) gc() end end launch: while true: MyThreadValue -> Set(3) Sleep(11) gc() end end while true: PrintLn(MyThreadValue -> Get()) Sleep(100) gc() end end 


Coroutines


This functionality allows synchronized parallelization of code execution.

Code example:
 uses <bf> uses <crt> uses <threads> proc class::Proc1(): while true: println("Hello world #1") sleep(100) gc() $yield() end end proc class::Proc2(): while true: println("Hello world #2") sleep(100) gc() $yield() end end proc class::Proc3(): while true: println("Hello world #3") sleep(100) gc() $yield() end end proc main(): cor3 ?= new TCoroutine(false, null) cor3 -> Execute ?= class::Proc3 cor2 ?= new TCoroutine(false, cor3) cor2 -> Execute ?= class::Proc2 cor1 ?= new TCoroutine(false, cor2) cor1 -> Execute ?= class::Proc1 cor3 -> NextCoroutine ?= cor1 cor1 -> Resume() InputLn() end 


Conclusion:
 Hello world #1 Hello world #2 Hello world #3 Hello world #1 Hello world #2 Hello world #3 ... 


Conclusion


I hope this article was interesting to you.

Waiting for comments :)

PS: According to your remarks, I have removed the construction until..end from the language. Now its place is taken by the construction:

 whilst <>: ... end 

It represents the usual while loop, with the difference that the condition is checked after iteration.

Source: https://habr.com/ru/post/442560/


All Articles