Mash: multithreading, coroutines, assíncrono e espera

imagem

Prefácio


Deixe-me lembrá-lo de que esse idioma foi desenvolvido por mim para fins educacionais como parte de um hobby. Não o considero (no momento) uma linguagem idealmente desenvolvida, mas quem sabe o que o futuro pode esperar.

Se você deseja experimentá-lo em ação - faça o download do repositório do projeto, nele você pode encontrar a versão montada do projeto ou montá-lo você mesmo, para o seu sistema operacional.

1. Introdução


Multithreading e assincronismo em nossos dias são um dos componentes mais importantes das linguagens de programação modernas.

Portanto, decidi adicionar suporte a projetos e tecnologias modernas à minha linguagem de programação, em parte adicionando projetos simples e convenientes à linguagem.

Fluxos rápidos


Eles facilitam o paralelismo da execução do código.
Para isso, o lançamento: ... construção final foi adicionada ao Mash

Exemplo 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 

Exemplo de saída:

 9 1 2 7 5 3 10 4 6 8 

Quando a execução do programa atinge launch..end, o código dentro desse bloco é iniciado em um thread separado, e a execução do código de inicialização é transferida para esse bloco.

Você pode encontrar quase a mesma construção de linguagem anteriormente na linguagem de programação Kotlin.

Assinatura e espera


A implementação de corotinas por si só não é suficiente para mim, é por isso que as construções assíncronas e de espera também são adicionadas ao Mash.

O Async permite converter a execução do código em um thread separado e continuar a execução do código principal.

A espera permite aguardar o momento em que todos os blocos assíncronos necessários forem concluídos.

Exemplo 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 

Conclusão:

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

Multithreading clássico


A principal base de código que fornece suporte para multithreading está concentrada no módulo? <threads>.

Os principais componentes que serão discutidos posteriormente:

1) Classe TThread (apenas uma declaração de classe é fornecida, o código completo fica mais adiante no 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) classe TCriticalSection (sua descrição):

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


3) Métodos para criar e iniciar threads rapidamente:
 func Async(method, ...) func Thread(method, ...) func Parallel(method, ...) 


4) Atômico seguro para thread (classe variável para interação entre threads):
 class TAtomic: private: var Locker, Value public: proc Create, Free proc Set func Get end 


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


Então, vamos colocar em ordem.

A classe TThread nos permite criar uma nova classe sucessora com base nela, adicionando as variáveis ​​necessárias aos seus campos, que serão transferidas para o novo thread.

Código de exemplo imediato:

 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 

Se estivermos com preguiça de descrever uma nova classe para criar um fluxo, podemos recuperar o suporte para a redefinição dinâmica de métodos em instâncias de classe e usá-lo.

Exemplo 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 

Se precisarmos apenas executar o método com os parâmetros em um novo thread, os métodos async (), thread () e parallel () são exatamente o que precisamos.

Um exemplo de como iniciar um método em um novo 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 

Como você deve ter notado anteriormente, esses três métodos são funções e retornam - classes semelhantes ao TThread.

A diferença deles é que async () cria um encadeamento que, após a conclusão, libera memória de si mesmo, e uma instância da classe TThread é excluída automaticamente,
thread () é o mesmo que assíncrono (), somente o thread é criado inicialmente congelado.
E finalmente paralelo () - cria um encadeamento em execução, que após a conclusão não executará autodestruição, ou seja, podemos usar qualquer método da classe TThread, por exemplo, WaitFor () e não ter medo de erros de tempo de execução. A única ressalva - você precisará ligar para Free () manualmente.

Sincronização de threads


Para fazer isso, adicionei a classe TCriticalSection ao Mash.

Exemplo 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 


Atomic


Implemente um contêiner seguro para threads para armazenar quaisquer valores.

Exemplo 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 


Coroutines


Essa funcionalidade permite sincronizar a execução paralela do código.

Exemplo 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 


Conclusão:
 Hello world #1 Hello world #2 Hello world #3 Hello world #1 Hello world #2 Hello world #3 ... 


Conclusão


Espero que você ache este artigo interessante.

À espera de comentários :)

PS: De acordo com seus comentários, eu removi o construto till..end do idioma. Agora seu lugar é ocupado pela construção:

 whilst <>: ... end 

É um loop while regular, com a diferença de que a condição é verificada após a iteração.

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


All Articles