- É difícil definir o que é assíncrono.
- O termo assíncrono é sobre o que não é, isto é, não ser síncrono.
- Isso é devido a etimologia da palavra. A letra a refere-se a negação ou privação.
- O termo síncrono refere-se a processos que acontecem um após o outro.
- Por que não querer que algo seja síncrono? É para obter concorrência e paralelismo.
- Os termos concorrência e paralelismo referem-se a coisas que acontecem ao mesmo tempo.
- Nem sempre precisamos que as coisas aconteçam uma após a outra.
- O fogão de cozinha, por exemplo, possuem várias bocas para que seja possível cozinharmos várias coisas de uma vez.
- E isso é resolvido na computação usando threads e processos.
- Uma thread é uma linha de execução dentro de um processo.
- O termo concorrência refere-se a troca rápida de linhas de execução e tem-se a impressão de que as coisas acontecem ao mesmo tempo mas, na verdade, apenas uma coisa é executada por vez pelo processador.
- Em um processo pode-se ter uma ou mais threads.
- Recomenda-se utilizar analogias para compreender a programação assíncrona.
- Uma analogia bastante conhecida é a do garçom no restaurante.
- A jornada normal de um garçom dentro do restaurante é a seguinte:
- receber cliente
- levar até uma mesa livre ou reservada
- anotar o pedido
- solicitar o prato pra cozinha
- levar o prato pronto da cozinha até o cliente
- Se considerarmos o garçom síncrono os passos serão o seguinte:
- receber cliente
- levar até uma mesa livre ou reservada
- esperar o cliente escolher
- anotar o pedido
- solicitar o prato pra cozinha
- esperar a cozinha fazer o prato
- levar o prato pronto da cozinha até o cliente
- Ter um garçom síncrono funciona somente para um cliente por vez no restaurante e isso é inviável.
- E, para resolver esse problema, deve-se ter um garçom assíncrono. Isto é, enquanto um cliente escolhe o prato, o garçom atende outro cliente.
- Se considerarmos o garçom assíncrono os passos serão o seguinte:
- receber cliente
- levar até uma mesa livre ou reservada
- já vai atender outro cliente
- anotar o pedido
- solicitar o prato pra cozinha
- atender um novo cliente ou levar um prato pronto
- levar o prato pronto da cozinha até o cliente
- Um garçom assíncrono faz coisas de forma concorrente, isto é, ele não está fazendo as coisas ao mesmo tempo e sim trocando de contexto várias vezes.
- Uma pessoa multitarefa consegue trocar de foco várias vezes e trabalha de forma concorrente.
- No decorrer da evolução da computação começaram a surgir processadores com vários núcleos.
- Contudo, existem aplicações que utilizam apenas um núcleo do processador enquanto os outros ficam ociosos.
- Como solucionar esse problema de CPUs ociosas? Criar inúmeras threads e processos?
- As threads e processos foram criados para permitir a execução concorrente mas nem sempre são as melhores opções.
- Ao utilizar threads, pode-se ter os seguintes problemas:
- as trocas de contexto entre threads não são operações leves
- as threads compartilham memória com outras threads em um mesmo processo e isso é pode acarretar problemas como condições de corrida e deadlocks pela necessidade de uso de mutexes
- Ao utilizar processos, pode-se ter os seguintes problemas:
- Os processos são muito pesados.
- Quando se faz um fork de um processo filho ele utiliza toda memória do processo pai.
- Neste caso, a criação de muitos processos podem consumir muito recurso da máquina.
- Os processos são úteis em alguns cenários como, por exemplo, criar um processo para cada aba de um navegador.
- Mas, para outros cenários, é uma operação cara criar inúmeros processos.
- O Apache utilizava uma abordagem em que um processo pai criava processos filhos e cada um deles tinha uma pool de threads para lidar com a requisição.
- O PHP-FPM utiliza a abordagem de ter um pool de processos do interpretador PHP para lidar com as requisições.
- Com o passar do tempo, a web evoluiu e passou-se a ter gargalo de requisições.
- Isso levou ao problema C10K cunhado por Dan Kegel em 1999.
- Esse problema propunha que um servidor deveria aguentar 10.000 requisições dentro de uma mesma máquina.
- "It's time for web servers to handle ten thousand clients simultaneously, don't you think? After all, the web is a big place now." - Dan Kegel
- Em 2010 o WhatsApp alcançou a marca de lidar com 2 milhões de requisições em uma única máquina com 24 cores utilizando Erlang.
- A linguagem e a máquina virtual do Erlang inspiraram a Swoole.
- A linguagem Erlang foi criada pela Ericsson há mais de 30 anos atrás.
- Em 2002 o russo Igor Sysoev resolveu o problema do C10K.
- Ele observou que a web é sobre I/O, isto é, unix sockets, sockets TCP e UDP, ler e salvar arquivos, pipes (stdin, stdout e stderr) e dispositivos.
- O conceito de I/O não tem nada a ver com computação pesada e cálculos matemáticos.
- Quando uma operação de I/O é iniciada no sistema operacional ela é identificada em uma tabela no kernel com um id conhecido como file descriptor.
- Sempre que um processo de I/O é iniciado, tem-se no kernel um file descriptor correspondente a esse processo de I/O que está sendo executado.
- Uma ideia é ler o status do file descriptor dentro do kernel para saber se ele estava pronto, isto é, se um arquivo já foi lido ou se já foram recebidos os pacotes TCP.
- A leitura do file descriptor não é suficiente pois trata-se ainda de uma operação bloqueante e era necessário uma operação não bloqueante.
- Uma ideia era fazer a chamada e não bloquear e, então, ficar chamando o kernel para ver se o arquivo está pronto para a leitura.
- Isto é, é possível implementar I/O não bloqueante com as chamadas de sistema
select()
epoll()
. - Contudo, fazer chamadas de sistema não é uma operação barata.
- A mecânica de ficar chamando algo para verificar se está pronto é chamada de pooling, logo a chamada de sistema no Linux é a
poll()
. - O que resolveu o problema C10K foi o I/O multiplexing e não as abordagens com as chamadas de sistema
select()
epoll()
. - O I/O multiplexing é utiliza event loop e é controlado por chamadas de sistema mais eficientes como a
epoll()
que tem complexidade O(1). - Existem implementações para outros sistemas operacionais que são eficientes como a IOCP para Windows e kqueue para Mac e FreeBSD.
- Na biblioteca de epoll existe uma chamada de sistema
epoll_wait()
que recebe um file descriptor e espera um evento acontecer. - Essa é uma abordagem eficiente pois em vez de ficar perguntando toda vez para o kernel, quando algo acontecer o kernel notifica.
- A utilização da
epoll()
resolveu o problema C10K e resolve até hoje problemas similares. - O Nginx utilizou a ideia de I/O multiplexing para permitir um maior desempenho.
- O fluxo de funcionamento do Nginx é o seguinte:
- os clientes enviam requisições e recebem respostas do event loop do Nginx
- o event loop é single threaded, não bloqueante e responsável por delegar operações lentas e de I/O para workers
- os workers processam essas operações e retornam para o event loop que, por sua vez, retorna para os clientes
- O conceito de programação orientado a eventos já não era nenhuma novidade devido a softwares desktop que lidavam com eventos como clique de botão.
- A novidade foi ter um event loop na frente de um I/O multiplexing e ficou conhecido como Reactor Pattern.
- As ferramentas que implementam o Reactor Pattern são o Tornado de Python e o Node.js de JavaScript.
- O Node.js surgiu em 2009 e foi importantíssimo para o ecossistema JavaScript.
- Na mesma época, o Tianfeng Han em Xangai, implementou as mesmas ideias na linguagem PHP quando teve um problemas com inbound de e-mail.
- A Swoole é uma runtime de I/O não bloqueante para resolver problemas de assincronia.
- A palavra Swoole vem da palavra sword pois é rápido e preciso como uma espada ou fast and sharp em inglês.
- A trajetória do projeto Swoole é a seguinte:
- 2012
- o projeto ficou open-source no GitHub
- 2013
- saiu a versão 1 do Swoole
- 2016
- saiu a versão 2 do Swoole com corrotinas
- 2017
- a Transfon adotou o projeto e começou a patrociná-lo
- o patrocínio permitiu que os desenvolvedores dedicassem mais tempo para o projeto
- houve uma "revolução" na ferramenta
- não teve a versão 3 (foi da 2 para a 4)
- o módulo de corrotinas foi reescrito
- 2018
- o módulo de corrotinas da v4 utilizou a biblioteca de corrotinas da Tencent (libco)
- a Tencent é a maior empresa da Ásia
- a Swoole utiliza a biblioteca de corrotinas (libco) da Tencent e a Tencent utiliza a Swoole
- a Tencent é a empresa por trás da QQ, WeChat, Epic, Miniclip e Riot (LoL e Valorant)
- existe game server da Epic implementado em Swoole
- a Baidu usa Swoole
- existe uma página dos cases no site
- 2012
- A Convenia utilizou a Swoole e teve ganhos incríveis em desempenho e redução de custos na nuvem.
- O Victor Gazoti escreveu um artigo com sobre o processo de implementação usando Swoole.
- O nome do artigo é How we increased our PHP app performance by 80% with Laravel and Swoole - Victor Gazoti.
- O Swoole é multithreaded e aproveitar melhor o poder computacional da máquina semelhante ao Erlang.
- O Swoole possui os seguintes componentes:
- Reactor - é o padrão para ler I/O
- Worker - administram tarefas I/O bound
- TaskWorker - administram tarefas CPU bound
- Algumas runtimes como a Swoole entregam os recursos da máquina para a máquina virtual e possibilitam a criação de "threads internas" (que não são kernel threads).
- Essa abordagem é conhecida como m:n threading.
- As green threads ou lightweight threads são as threads internas criadas por uma runtime como a BEAM do Erlang que é responsável pelo mapeamento com as threads do sistema operacional.
- A Erlang chama de processo uma unidade de computação que é executada de forma assíncrona.
- Pode-se utilizar fibers e generators para lidar com programação assíncrona.
- A Swoole escolheu utilizar corrotinas para lidar com uma unidade de computação que executa de forma assíncrona.
- As corrotinas são estruturas que cooperam entre si, isto é, são processos que podem se iniciar, parar, dar a vez para outra e assim por diante.
- A linguagem Go possui o conceito de corrotinas.
- A Swoole é uma extensão do PHP e pode ser instalada utilizando o comando
pecl install swoole
. - A Swoole utiliza as chamadas de sistema
epoll
ekqueue
, isto é, pode ser utilizada no Linux e no Mac. - Ela não funciona no Windows mas pode ser utilizada com Docker e WSL.
- A linguagem Go inspirou a Swoole e utiliza-se a função
go()
para executar uma corrotina.
// example 1a - anonymous function
go(function() {
echo 'Hello, Swoole!';
});
// example 1b - arrow function
go(fn () => print('Hello, Swoole!'));
// example 1c - normal function
function greet() {
echo 'Hello, Swoole!';
}
go(fn () => greet());
// example 2a
function something_slow()
{
sleep(1);
}
// run synchronously and takes 3s
something_slow();
something_slow();
something_slow();
// example 2b
Swoole\Runtime::enableCoroutine();
function something_slow()
{
sleep(1);
}
// run asynchronously and takes 1s
go('something_slow');
go('something_slow');
go('something_slow');
- A instrução
Swoole\Runtime::enableCoroutine()
transforma o funcionamento interno do PHP de síncrono para assíncrono. - Ela faz um hijacking de funcionalidades internas fazendo-as executar dentro de corrotina.
- O hijacking é feito na php stream que é uma construção da Zend VM e tudo que a utiliza vai executar dentro de corrotinas como MySQLi, PDO e a extensão de Redis.
- O hijacking também habilita o funcionamento de corrotinas em extensões que são bastante utilizadas como o Curl.
- O Curl é, na verdade, uma extensão PHP que faz o binding para a
libcurl
em C. - Pode-se utilizar Communicating Sequential Process ou CSP para resolver a comunicação entre as unidades de processamento assíncrona.
- O Communicating Sequential Process sugiu de um paper de Sir Tony Hoare, cientista da computação incrível e responsável pela criação do null.
- As corrotinas se comunicam através de message passing.
- Os channels são uma implementação de message passing e são utilizadas na Swoole e em Go.
- Existem outras APIs para trabalhar com corrotinas como waitgroup, barrier, batch e parallel.
- O async e await são um açúcar sintático para trabalhar com programação assíncrona.
- Pode-se utilizar um preemptive scheduler para orquestrar as corrotinas.
- Pode-se habilitar um preemptive scheduler na Swoole.
Swoole\Runtime::enableScheduler();
ini_set("swoole.enable_preemptive_scheduler", "1");
- A Swoole, apesar de influências de Go e Erlang, tem uma fundação forte e caminha com as próprias pernas. A inclusão do preemptive scheduler, por exemplo, foi feita antes mesmo de Go.
- Existe uma Awesome List de Swoole com inúmeras referências como bindings de Mezzio e Laravel para Swoole.
- O que é um unix socket?
- O que é um pool de threads e processos?
- O que spawn de processos?
- O que é uma syscall ou chamada de sistema?
- O que é I/O multiplexing?
- O que é CPU bound, IO bound e memory bound?
- O que é green thread?
- O que é corrotina?
- O que é hijacking?
- O que é communicating sequential process?
- Simple explanation of the Unix sockets - Stack Exchange
- Pool de Threads
- Múltiplas Threads vs Única Thread - Stack Overflow
- O que é uma system call no Linux? - Mateus Muller
- Chamadas de Sistema - Guia Linux
- Unix System Calls (part 1) - Brian Will
- Unix System Calls (part 2) - Brian Will
- Linux System Call Table
- List of Linux Syscalls - linuxhint
- Linux System Call Table
- Syscall select - Linux Programmer's Manual
- Syscall poll - Linux Programmer's Manual
- Syscall epoll_create - Linux Programmer's Manual
- Syscall epoll_wait - Linux Programmer's Manual
- How we increased our PHP app performance by 80% with Laravel and Swoole - Victor Gazoti
- CPU Bound, I/O Bound e Memory Bound
- Introduction to Communicating Sequential Processes (CSP) - Youtube
- O que são corrotinas? - Vinicius Dias
- O que são corrotinas? - Stack Overflow
- Introdução ao Swoole, framework PHP assíncrono baseado em corrotinas - Kennedy Tedesco
- Trabalhando com corrotinas, canais e explorando um pouco mais o scheduler de corrotinas do Swoole - Kennedy Tedesco
- Awesome List de Swoole