Mash: multiproceso, corutinas, asíncrono y espera

imagen

Prólogo


Permíteme recordarte que este lenguaje fue desarrollado por mí con fines educativos como parte de un pasatiempo. No lo considero (por el momento) un lenguaje perfectamente desarrollado, pero quién sabe qué puede esperar el futuro.

Si desea probarlo usted mismo en acción, descargue el repositorio del proyecto, en él puede encontrar la versión ensamblada del proyecto o ensamblarlo usted mismo, para su sistema operativo.

Introduccion


El subprocesamiento múltiple y el asincronismo en nuestro tiempo son uno de los componentes más importantes de los lenguajes de programación modernos.

Por lo tanto, decidí agregar soporte para diseños y tecnologías modernas a mi lenguaje de programación, en parte agregando diseños simples y convenientes al lenguaje.

Flujos rápidos


Facilitan la paralelización de la ejecución de código.
Para esto, el lanzamiento: ... la construcción final se ha agregado a Mash

Ejemplo de código:

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

Ejemplo de salida:

 9 1 2 7 5 3 10 4 6 8 

Cuando la ejecución del programa llega al inicio ... final, el código dentro de este bloque se inicia en un hilo separado, y la ejecución del código de inicio se transfiere a este bloque.

Podrías encontrar casi la misma construcción de lenguaje anteriormente en el lenguaje de programación Kotlin.

Asíncrono y espera


La implementación de corutinas por sí sola no es suficiente para mí, es por eso que las construcciones asíncronas y de espera también se agregan a Mash.

Async le permite traducir la ejecución del código en un hilo separado y continuar la ejecución del código principal.

Esperar le permite esperar el momento en que se completen todos los bloques asíncronos necesarios.

Ejemplo de código:

 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 

Conclusión

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

Multihilo clásico


La base del código principal que proporciona soporte para subprocesos múltiples se concentra en el módulo? <threads>.

Los componentes principales que se discutirán más adelante:

1) Clase TThread (solo se proporciona una declaración de clase, el código completo se encuentra más adelante en el módulo):

 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) Clase TCriticalSection (su descripción):

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


3) Métodos para crear y comenzar subprocesos rápidamente:
 func Async(method, ...) func Thread(method, ...) func Parallel(method, ...) 


4) Atómico seguro para subprocesos (clase variable para interacción entre subprocesos):
 class TAtomic: private: var Locker, Value public: proc Create, Free proc Set func Get end 


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


Entonces, tomémoslo en orden.

La clase TThread nos permite crear una nueva clase sucesora basada en ella, agregando las variables necesarias a sus campos, que se transferirán al nuevo hilo.

Código de muestra inmediato:

 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 

Si somos demasiado vagos para describir una nueva clase para crear una secuencia, entonces podemos recordar el soporte para la redefinición dinámica de métodos en instancias de clase y usarlo.

Ejemplo de código:

 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 

Si solo necesitamos ejecutar el método con los parámetros en un nuevo hilo, entonces los métodos asíncrono (), hilo () y paralelo () son exactamente lo que necesitamos.

Un ejemplo de inicio de un método en un nuevo hilo:

 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 

Como habrás notado anteriormente, estos 3 métodos son funciones y regresan, clases similares a TThread.

Su diferencia es que async () crea un subproceso que, al finalizar, libera memoria de sí mismo, y una instancia de la clase TThread se elimina automáticamente,
thread () es lo mismo que async (), solo el thread se crea inicialmente congelado.
Y, finalmente, paralelo (): crea un subproceso en ejecución que, una vez completado, no se autodestruirá, es decir Podemos usar cualquier método de la clase TThread, por ejemplo WaitFor () y no tener miedo a los errores de tiempo de ejecución. La única advertencia: deberá llamar a Free () manualmente.

Hilo de sincronización


Para hacer esto, agregué la clase TCriticalSection a Mash.

Ejemplo de código:

 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 


Atómico


Implemente un contenedor seguro para subprocesos para almacenar cualquier valor.

Ejemplo de código:
 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 


Corutinas


Esta funcionalidad le permite sincronizar la ejecución paralela de código.

Ejemplo de código:
 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 


Conclusión
 Hello world #1 Hello world #2 Hello world #3 Hello world #1 Hello world #2 Hello world #3 ... 


Conclusión


Espero que encuentres este artículo interesante.

Esperando comentarios :)

PD: De acuerdo con sus comentarios, eliminé la construcción hasta ... end del lenguaje. Ahora su lugar lo ocupa la construcción:

 whilst <>: ... end 

Es un ciclo while regular, con la diferencia de que la condición se verifica después de la iteración.

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


All Articles