Eventos

Trabalhando com corrotinas, canais e explorando um pouco mais o scheduler de corrotinas do Swoole

Neste artigo veremos de forma prática os aspectos essenciais do modelo de programação concorrente CSP (communicating sequential processes) com Swoole, usando Coroutine (corrotina), Channel (canal) e Defer (execução tardia). Se você já programou em Go verá muitas similaridades.

Antes, entretanto, é fundamental que você leia o artigo Introdução ao Swoole, framework PHP assíncrono baseado em corrotinas, pois ele introduz toda a teoria fundamental para que possamos criar os nossos primeiros exemplos e adentrar um pouco mais nas possibilidades que o Swoole nos oferece.

PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

Em uma execução sequencial e síncrona de duas funções, teríamos:

<?php

function a() {
    sleep(1);
    echo 'a';
}

function b() {
    sleep(2);
    echo 'b';
}

a();
b();

O resultado é bem previsível, aguarda um segundo, imprime a, aguarda dois segundos e imprime b.

Para que possamos executar uma tarefa dentro de uma corrotina, usamos a função go(). O exemplo acima poderia ser reescrito para:

<?php

go(static function () {
    sleep(1);
    echo 'a';
});

go(static function () {
    sleep(2);
    echo 'b';
});

O problema que temos agora é que a função sleep() do PHP é bloqueante, assim como são as funções de stream, por exemplo.

Recomendação de leitura: Streams no PHP

Esse exemplo terá o exato mesmo comportamento que o anterior. Ele demorará três segundos pra finalizar a sua execução. Podemos resolver isso de duas formas, sendo que a primeira é adicionando a instrução Swoole\Runtime::enableCoroutine(); no exemplo:

<?php

Swoole\Runtime::enableCoroutine();

go(static function () {
    sleep(1);
    echo 'a';
});

go(static function () {
    sleep(2);
    echo 'b';
});

Este é um hook “mágico” que fará com que o Swoole execute algumas funções que são nativamente síncronas mas de forma assíncrona (não bloqueante). E isso vale para a sleep(), como vale para as funções relacionadas a streams.

Agora sim, esse exemplo será executado em dois segundos. Ao invés da execução consumir a soma dos dois tempos das corrotinas, ela passa a consumir o tempo da maior.

Então, temos a seguinte relação:

  • No modelo síncrono gasta-se o tempo de: (a + b)
  • No modelo concorrente gasta-se: MAX(a, b)

A outra forma de resolver o problema anterior sem que precisemos aplicar o hook enableCoroutine(), é executando a função sleep() assíncrona da API do Swoole:

<?php

use Swoole\Coroutine\System;

go(static function () {
    System::sleep(1);
    echo 'a';
});

go(static function () {
    System::sleep(2);
    echo 'b';
});

Outras funções disponíveis na API de corrotinas:

System::sleep(100);
System::fread($fp);
System::gethostbyname('www.google.com');
// Entre outras

Você verá muitos System::sleep() até o final desse artigo, pois é uma forma de emular uma operação de I/O (que é sabido que é mais custosa que uma operação de CPU).

Executando os exemplos

Se você usa Linux ou macOS pode instalar o Swoole diretamente no seu ambiente:

https://www.swoole.co.uk/docs/get-started/installation

Ou você pode usar o Docker, que é a opção escolhida desse artigo. Algumas das imagens disponíveis para o Swoole:

Para esse artigo eu estou usando como base a imagem do swoole-by-examples. Todos os exemplos desse artigo estão disponíveis nesse repositório:

https://github.com/KennedyTedesco/swoole-coroutines

Basta que você clone-o em seu computador e então execute o comando abaixo para inicializar o container:

Docker - Fundamentos
Curso de Docker - Fundamentos
CONHEÇA O CURSO
$ docker-compose up -d

E para executar o exemplo anteriormente criado:

$ docker-compose exec client bash -c "time php ./co1.php"

O resultado no terminal será:

ab
real    0m2.031s
user    0m0.010s
sys 0m0.010s

Usamos time na execução para que possamos ter a informação do tempo gasto.

Uma coisa importante de se pontuar é que esse projeto tem como dependência no composer o swoole-ide-helper que ajuda a sua IDE ou editor de código reconhecer as assinaturas das classes e métodos do Swoole. Mas é bom sempre lembrar que a documentação é outro ótimo lugar para conhecer outros detalhes e características das APIs.

Voltando …

Um importante conceito de concorrência é que não é sobre execução ordenada, a ordem de execução das tarefas não é garantida, são vários os fatores que influenciam. Então, o observe o exemplo abaixo em que executamos 5000 corrotinas:

<?php

use Swoole\Coroutine\System;

for ($i = 0; $i < 5000; $i++) {
    go(static function () use ($i) {
        System::sleep(1);
        echo "$i\n";
    });
}

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co2.php"

Sempre que você executá-lo, terá um retorno diferente. Nesse exemplo criamos 5000 corrotinas que foram executadas em cerca de 1s. Não fossem executadas de forma concorrente gastaríamos 5000 segundos.

Outras formas de criar corrotinas

A função go() é muito conveniente para a criação de corrotinas, bastando que passemos para ela uma função anônima representando a tarefa. No entanto, existem outras formas de utilizá-la. O primeiro parâmetro dela espera por um callable:

/**
 * @param callable $func
 * @param ...$params
 * @return mixed
 */
function go(callable $func, ...$params){}

Portanto, poderíamos passar o nome de uma função:

<?php

use Swoole\Coroutine\System;

function someTask(int $i) : void {
    System::sleep(1);

    echo "$i\n";
}

for ($i = 0; $i < 1000; $i++) {
    go('someTask', $i);
}

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co3.php"

Como também poderíamos passar a instância de um objeto invocável:

<?php

use Swoole\Coroutine\System;

final class SomeTask
{
    public function __invoke(int $i): void
    {
        System::sleep(1);

        echo "$i\n";
    }
}

for ($i = 0; $i < 1000; $i++) {
    go(new SomeTask, $i);
}

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co4.php"

E as outras formas possíveis são:

<?php

use Swoole\Coroutine\System;

function someTask(int $i): void {
    System::sleep(1);

    echo "$i\n";
}

co::create('someTask', 1);

swoole_coroutine_create('someTask', 2);

Swoole\Coroutine::create('someTask', 3);

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co5.php"

E todas elas aceitam um valor callable, são formas alternativas a go().

Outro conceito importante sobre corrotinas no Swoole é que o scheduler delas não é multi-threaded como em Go. Apenas uma corrotina é executada por vez, não são executadas em paralelo. Por exemplo, se temos duas tarefas e a tarefa 1 é executada, se tem um sleep(1) nela, essa tarefa é pausada e então a tarefa 2 é executada, depois o scheduler volta para a tarefa 1. Eventos de I/O pausam/resumem a execução das corrotinas a todo instante.

Canais

Outro ponto fundamental do modelo CSP são os canais. As corrotinas representam as atividades do programa e os canais representam as conexões entre elas. Um canal é basicamente um sistema de comunicação que permite uma corrotina enviar valores para outra. Em Go um canal precisa ter um tipo especificado previamente, enquanto que no Swoole podemos armazenar qualquer tipo de dado.

Um exemplo:

<?php

use Swoole\Coroutine\System;
use Swoole\Coroutine\Channel;

$chan = new Channel();

go(static function () use ($chan) {
    // Cria 10.000 corrotinas
    for ($i = 0; $i < 10000; $i++) {
        go(static function () use ($i, $chan) {
            // Emula uma operação de I/O
            System::sleep(1);

            // Adiciona o valor processado no canal
            $chan->push([
                'index' => $i,
                'value' => random_int(1, 10000),
            ]);
        });
    }
});

go(static function () use ($chan) {
    while (true) {
        $data = $chan->pop();
        echo "{$data['index']} -> {$data['value']}\n";
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./chan1.php"

Usamos o método push() para adicionar um item no canal, que no caso foi um array, mas poderia ser um inteiro, uma string etc. E usamos pop() para extrair um valor do canal. O while(true) dentro dessa corrotina em especial não é um problema, nisso que estamos realizando uma operação no canal, o estado dessa corrotina é controlado, ela não toma pra ela todo o tempo da CPU. Mas veremos mais adiante que operações pesadas de CPU podem impedir que outras corrotinas tenham a chance de serem executadas, mas isso pode ser resolvido se ativarmos o scheduler preemptivo do Swoole.

Avaliando URLs de forma concorrente

Um dos bons exemplos para visualizarmos na prática concorrência é quando envolvemos operações de rede na jogada. O exemplo que veremos a seguir, apesar de não tão sofisticado, foi desenvolvido para que possamos fazer uso de corrotinas, canais e defer.

<?php

use Swoole\Coroutine\Channel;
use Swoole\Coroutine\System;
use Swoole\Coroutine\Http\Client;

function httpHead(string $url) {
    $client = new Client($url, 80);
    $client->get('/');

    return $client;
}

$chan = new Channel();

go(static function () use ($chan) {
    // Abre um ponteiro para o arquivo
    $fp = fopen('sites.txt', 'rb');

    // Atrasa o fechamento do ponteiro do arquivo para o final da corrotina
    defer(static function () use ($fp) {
        fclose($fp);
    });

    while (feof($fp) === false) {
        // Lê linha a linha do arquivo
        $url = trim(System::fgets($fp));

        if ($url !== '') {
            // Cria uma corrotina para requisitar a URL e trazer o status code dela
            go(static function () use ($url, $chan) {
                $response = httpHead($url);

                // Insere no canal a resposta
                $chan->push([
                    'url' => $url,
                    'statusCode' => $response->statusCode,
                ]);
            });
        }
    }
});

// Corrotina que lê os valores do canal e imprime no output
go(static function () use ($chan) {
    while (true) {
        $data = $chan->pop();
        echo "{$data['url']} -> {$data['statusCode']}\n";
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./http1.php"

Na função httpHead() estamos usando o cliente HTTP de corrotina do Swoole, a documentação dele pode ser consultada aqui.

Na primeira corrotina abrimos um ponteiro para o arquivo onde as URLs estão localizadas. A função defer() define uma tarefa para ser executada ao final da corrotina, então a estamos utilizamos para fechar o ponteiro de arquivo aberto anteriormente.

Iteramos sobre cada linha do arquivo usando a função assíncrona co::fgets() da própria API de corrotina e então, pra cada URL, criamos uma nova corrotina para fazer uma requisição HEAD e obter o código http da resposta. Essa corrotina envia para um canal o resultado, canal este que é utilizado pela segunda corrotina, que imprime todos os valores contidos nele.

O cliente HTTP padrão do Swoole não possui uma API muito rica e não é tão intuitivo de se usar, para isso existe a biblioteca saber que encapsula toda a parte complicada, oferecendo uma API bem intuitiva e de alto nível para se trabalhar com requisições http concorrentes. Se você tiver interesse em praticar, recomendo alterar o exemplo anterior para usar a saber.

E como ficam as tarefas que fazem um uso intensivo de CPU?

Corrotinas são conhecidas por operarem por cooperação (a tarefa é dona do seu ciclo de vida, tendo o poder de se liberar do scheduler no fim de sua operação) em detrimento à preempção.

Esse diagrama ilustra melhor esse cenário:

Diagrama corrotinas

Enquanto nossas tarefas fazem mais uso de I/O que de CPU, tá tudo bem, pois deixamos os reactors fazerem a mágica. Agora, e se tivermos tarefas de uso pesado de CPU? O modo padrão do scheduler funcionar pode não ser o mais “justo” dependendo do caso, por exemplo:

<?php

use Swoole\Coroutine\System;

// Tarefa 1
go(static function() {
    System::sleep(1);
    for ($i = 0; $i <= 10; $i++) {
        echo "N{$i}";
    }
});

// Tarefa 2
go(static function() {
    $i = 0;
    while (true) {
        $i++;
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./scheduler1.php"

Ao executar esse exemplo, você notará que a primeira tarefa não terá a oportunidade de executar a sua lógica de imprimir N1, N2 etc, pois quando ela é despachada pelo scheduler para um worker, a primeira linha dela é System::sleep(1); que simula uma operação de I/O, isso faz com que ela seja pausada para que outra tarefa da fila seja executada. O problema é que a tarefa 2 não é muito espirituosa, ela fica num loop infinito incrementando uma variável, com isso, ela não deixa nenhuma oportunidade para que a outra tarefa irmã seja executada, ou seja, ela não é tão colaborativa assim.

Já sabemos que uma tarefa é pausada quando ela está aguardando por alguma operação de I/O para dar oportunidade a outra tarefa desempenhar o seu trabalho. Podemos emular isso na prática usando como base o exemplo anterior:

<?php

use Swoole\Coroutine\System;

// Tarefa 1
go(static function() {
    System::sleep(1);
    for ($i = 0; $i <= 10; $i++) {
        echo "N{$i}";
    }
});

// Tarefa 2
go(static function() {
    $i = 0;
    while (true) {
        $i++;

        // Quando estiver no centésimo loop, emula uma operação de I/O
        if ($i === 100) {
            echo "{$i} -> ";

            System::sleep(1);
        }
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./scheduler2.php"

O resultado da execução desse exemplo é:

100 -> N0N1N2N3N4N5N6N7N8N9N10

No centésimo loop emulamos uma operação de I/O de 1 segundo, que fez com que a tarefa fosse pausada dando oportunidade para a tarefa 1 voltar a ser executada.

Como as corrotinas possuem controle do seu ciclo de vida, é possível que uma corrotina deliberadamente peça a suspensão do seu direito de execução para dar espaço a outra corrotina. É o que vemos nesse exemplo:

<?php

// Tarefa 1
$firstTaskId = go(static function() {
    echo 'a';
    co::yield();
    echo 'b';
    co::yield();
    echo 'c';
});

// Tarefa 2
go(static function() use($firstTaskId) {
    $i = 0;
    while (true) {
        $i++;

        if ($i === 1000 || $i === 2000) {
            echo " {$i} ";

            co::resume($firstTaskId);
        }
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./scheduler3.php"

O resultado:

a 1000 b 2000 c

Quando criamos uma corrotina imediatamente recebemos o id dela, por isso definimos a variável $firstTaskId. A primeira tarefa imprime a e então abre mão do seu direito de execução, o que faz com que a segunda tarefa seja executada. Quando o contador chega em 1000, a segunda tarefa abre mão do seu direito de execução para que especificamente a primeira tarefa volte a ser executada e então b é impresso. Mas depois de imprimir b, a primeira tarefa novamente abre mão do seu direito de execução e então chegamos no contador 2000 da segunda tarefa que a resume novamente imprimindo, por fim, c.

Ok, mas e se existisse uma forma do scheduler cuidar dessas questões e não deixar que uma tarefa “sacana” tome todo o tempo da CPU dedicado ao processo? Existe, é possível ativarmos o modo preemptivo. Quando ativamos o modo preemptivo no scheduler, ele passa a funcionar de forma parecida com o scheduler do sistema operacional, dando um tempo justo pra cada linha de execução, sem deixar que uma tarefa impeça as outras de serem executadas. Esse modo preemptivo foi adicionado recentemente e ele parece ter um impacto positivo em aplicações de alto porte que envolvem uma mistura considerável de tarefas CPU bound e I/O bound. Talvez pra sua aplicação não mude muita coisa, ou talvez mude, você teria que testar essa carga nos dois modos (cooperativo e preemptivo) e então ver qual faz mais sentido pro seu caso de uso.

De qualquer forma, voltando no nosso caso hipotético do while(true), usando o modo preemptivo, temos:

<?php

ini_set('swoole.enable_preemptive_scheduler', 1);

go(static function() {
    $i = 0;
    while (true) {
        $i++;
    }
});

go(static function() {
    for ($i = 0; $i <= 10; $i++) {
        echo "N{$i}";
    }
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./scheduler4.php"

O resultado:

N0N1N2N3N4N5N6N7N8N9N10

Veja que a primeira tarefa é um while (true) , mas como o modo preemptivo foi ativado, ela terá um tempo de CPU em milissegundos (no máximo 10ms) e então terá que abrir espaço para que outra tarefa seja executada, depois o tempo da CPU volta pra ela novamente, algo controlado automaticamente pelo scheduler.

Aninhamento de corrotinas

Como já vimos anteriormente, é possível aninharmos corrotinas, criando novas sub-corrotinas. Um bom exemplo para entender a ordem de execução de corrotinas aninhadas:

<?php

go(static function () { //T1
    echo "[init]\n";
    go(static function () { //T2
        go(static function () { //T3
            echo "co3\n";
        });

        echo "co2\n";
    });

    echo "co1\n";
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co6.php"

O resultado:

[init]
co3
co2
co1

Agora, a história muda quando as corrotinas realizam ou emulam alguma operação de I/O:

<?php

use Swoole\Coroutine\System;

go(static function () { //T1
    echo "[init]\n";
    go(static function () { //T2
        System::sleep(3);
        go(static function () { //T3
            System::sleep(2);
            echo "co3\n";
        });

        echo "co2\n";
    });

    System::sleep(1);
    echo "co1\n";
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co7.php"

O resultado será:

[init]
co1
co2
co3

WaitGroup

Com um “grupo de espera” podemos aguardar a finalização de algumas corrotinas antes que executemos alguma outra instrução:

<?php

use Swoole\Coroutine\System;
use Swoole\Coroutine\WaitGroup;

$wg = new WaitGroup();

go(static function () use ($wg) {
    $wg->add(3);

    go(static function () use ($wg) {
        System::sleep(3);
        echo "T1\n";
        $wg->done();
    });

    go(static function () use ($wg) {
        System::sleep(2);
        echo "T2\n";
        $wg->done();
    });

    go(static function () use ($wg) {
        System::sleep(1);
        echo "T3\n";
        $wg->done();
    });

    // Aguarda a execução das corrotinas do grupo antes de executar as instruções abaixo
    $wg->wait();

    echo "\n---- \ ----\n";
    go(static function () {
        echo "\n[FIM]\n";
    });
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co8.php"

O resultado será:

T3
T2
T1

---- \ ----

[FIM]

O método add() é para incrementar o contador de quantas corrotinas estão no grupo de espera, ele pode ser usado quantas vezes forem necessárias.

Devo me preocupar com race conditions?

Em implementações em que o scheduler usa o modelo multi-thread, como em Go, o desenvolvedor precisa se preocupar com o acesso aos recursos globais compartilhados, para garantir que duas ou mais corrotinas não os acessem ao mesmo tempo, o que invariavelmente causaria race conditions (condições de corrida). Mas esse não é o caso quando usamos Swoole, pois o scheduler dele é single-thread, portanto, não há necessidade de lockings.

Esse exemplo em Go que cria 5k gorrotinas incrementando uma variável global:

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int
var waitGroup sync.WaitGroup

func main() {
    for i := 0; i < 5000; i++ {
        waitGroup.Add(1)
        go increment()
    }

    waitGroup.Wait()
    fmt.Println(count)
}

func increment() {
    time.Sleep(1 * time.Second)

    count++
    waitGroup.Done()
}

Para executá-lo:

$ time go run go1.go

Você pode testá-lo inúmeras vezes e verá que sempre terá um resultado diferente de 5.000, exatamente por causa das race conditions que acontecem, uma gorrotina atropelando a outra na hora de acessar a variável global.

Go implementa uma ferramenta para identificar race conditions, bastando adicionar o parâmetro -race na execução:

$ time go run -race go1.go

Ele indicará que o programa é uma “fábrica” de race conditions:

==================
WARNING: DATA RACE
Read at 0x000001229360 by goroutine 8:
  main.increment()
...
Found 4 data race(s)
exit status 66

Para que as evitemos, podemos usar mutexes ou operações atômicas. Vamos com a primeira opção que é bem simples de assimilar:

package main

import (
    "fmt"
    "sync"
    "time"
)

var count int
var mu sync.Mutex
var waitGroup sync.WaitGroup

func main() {
    for i := 0; i < 5000; i++ {
        waitGroup.Add(1)
        go increment()
    }

    waitGroup.Wait()
    fmt.Println(count)
}

func increment() {
    time.Sleep(1 * time.Second)

    mu.Lock()
    count++
    mu.Unlock()

    waitGroup.Done()
}

Observe que envolvemos a operação de incremento com mu.Lock() e mu.Unlock() para garantir um único acesso por vez à variável global.

Ao executar novamente o exemplo:

$ time go run -race go1.go

O resultado:

5000

E não teremos nenhum erro da ferramenta de verificação de race conditions.

Podemos ter o mesmo exemplo escrito no Swoole sem que precisemos fazer nada de especial (em relação a locks etc):

<?php

use Swoole\Coroutine\System;
use Swoole\Coroutine\WaitGroup;

$count = 0;

go(static function() use(&$count) {
    $wg = new WaitGroup();

    for ($i = 0; $i < 5000; $i++) {
        $wg->add(1);

        go(static function () use($wg, &$count) {
            System::sleep(1);
            $count++;

            $wg->done();
        });
    }

    $wg->wait();

    echo $count;
});

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co9.php"

Usamos WaitGroup para fazer paralelo com a implementação em Go, mas nesse exemplo em especial, poderíamos ter cortado essa etapa e escrito assim:

<?php

use Swoole\Coroutine\System;

$count = 0;

Co\run(static function() use(&$count) {
    for ($i = 0; $i < 5000; $i++) {
        go(static function () use(&$count) {
            System::sleep(1);
            $count++;
        });
    }
});

echo $count;

Para executá-lo:

$ docker-compose exec client bash -c "time php ./co10.php"

Esse exemplo produz o mesmo resultado que o anterior. Co\run aguarda as corrotinas serem finalizadas antes de seguir o fluxo da execução.

O que mais posso fazer com corrotinas?

Muito mais. Os clientes de corrotinas atualmente implementados/suportados pelo Swoole:

E, claro, lembre-se sempre de acompanhar a documentação.

O que mais posso fazer com o Swoole?

Recomendo acompanhar a lista awesome-swoole. Muita coisa boa, frameworks, libraries etc.

Considerações finais

Vimos a teoria essencial de corrotinas que são atualmente o principal mecanismo interno do Swoole e cada vez mais ganharão importância em seu core.

Nos próximos artigos exploraremos outras APIs do Swoole. Até breve!

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

Introdução ao Swoole, framework PHP assíncrono baseado em corrotinas

O Swoole é um framework de programação assíncrona para PHP e já se posiciona como uma opção estável e confiável para se desenvolver de forma concorrente com alta performance e uso equilibrado de recursos. Ele implementa I/O assíncrono orientado a eventos baseado no padrão Reactor (o mesmo utilizado por ReactPHP, NodeJS, Netty do Java, Twisted do Python entre outros). O Swoole é escrito em C e disponibilizado como uma extensão para PHP.

O Swoole permite que escrevamos aplicações altamente performáticas e concorrentes usando TCP, UDP, Unix Socket, HTTP e WebSockets sem que precisemos ter grandes conhecimentos de baixo nível sobre assincronismo e/ou sobre o kernel do Linux. Ele fornece uma completa API de alto nível com foco na produtividade. O Swoole é usado em grandes projetos enterprises, principalmente na China sendo Baidu (maior portal de busca da China) e Tencent (um dos maiores portais de serviços da China) os principais casos de uso.

É muito importante e recomendado que antes de continuar aqui, você leia o artigo sobre Introdução à programação assíncrona em PHP usando o ReactPHP, pois ele vai te introduzir alguns importantes conceitos de como é o modelo tradicional síncrono de se desenvolver em PHP e como funciona o modelo assíncrono usando o padrão Reactor.

PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

Para o que veio e para o que não veio o Swoole

O Swoole não veio para substituir a abordagem tradicional e síncrona de como a maioria do projetos em PHP são escritos. Ao contrário disso, ele veio pra suprir os outros casos de uso como, por exemplo, a criação de servidores TCP, RPC, Websockets etc. Ele veio para resolver o problema de servidores que precisem de alta concorrência. Para se ter ideia, um único servidor escrito usando o Swoole consegue lidar com 1 milhão de conexões (C1000K). Também podemos dizer que o Swoole veio para que você não precise mudar toda sua stack e linguagem para resolver as necessidades acima listadas.

Muitas formas de se atingir assincronismo

Síncrono ou assíncrono é sobre o fluxo de execução. É possível atingir assincronismo de diversas formas como, por exemplo, uma fila de mensagens (Amazon SQS, Redis etc) onde você a alimenta com as tarefas (jobs) e no seu servidor você tem uma determinada quantidade de worker processes consumindo essa fila e executando as tarefas de forma concorrente e/ou paralela (a depender dos núcleos da CPU).

Também é possível atingir assincronismo com Ajax, lembra de requisição assíncrona onde é possível enviar a requisição e não ficar esperando pela resposta para poder fazer outra? Também é possível criando novos processos (uma opção mais pesada) ou criando novas threads (mais leve que processos, mas ainda assim, não tão leve quanto corrotinas que veremos adiante). Todas essas coisas podem ser trabalhadas para se chegar no objetivo de atingir assincronismo.

No entanto, o modelo assíncrono do Swoole é focado em nonblocking I/O e I/O multiplexing que é mais popularmente conhecido como event driven (com eventos de leitura e escrita a partir do monitoramento de descritores de arquivos) e usa chamadas de sistema como select, pool ou epool. Além disso, o Swoole implementa corrotinas com o modelo CSP (Communicating sequential processes) que é bem conhecido na linguagem Go com go, chan e defer. Se você já trabalhou com concorrência em Go, terá muita facilidade para assimilar em como as coisas são feitas no Swoole.

A arquitetura tradicional com PHP-FPM

Na arquitetura tradicional da maior parte das aplicações escritas em PHP, temos um servidor web e um process manager baseado no protocolo FastCGI, que no caso do PHP o oficial é o PHP-FPM. No lado do servidor web, o mais usado atualmente é o Nginx, que por sinal, também usa o padrão Reactor com I/O multiplexing (epool) para conseguir responder a milhares de conexões simultâneas, diferentemente do Apache (outro popular servidor) que implementa um modelo híbrido multi-process e multi-thread (quando usando Worker MPM em detrimento ao Prefork MPM).

É relativamente comum ler que o PHP-FPM é multi-thread, mas na realidade ele é multi-process. As requisições são iniciadas pelo servidor web (Nginx, por exemplo) que as redireciona para o PHP-FPM via o protocolo binário FastCGI. O Master Process do PHP-FPM recebe essas requisições e as aloca em um novo worker process. O PHP-FPM gerencia pools de worker processes, cada pool pode gerenciar um determinado número de processos filhos (worker processes) para lidar com as requisições (e esse número depende das configurações do PHP-FPM e da quantidade de memória disponível). Todo esse processo é bloqueante por natureza, ou seja, enquanto o script estiver sendo executado (acessando I/O, por exemplo). Só no final quando a resposta é processada e retornada que o processo filho criado pelo PHP-FPM é reciclado.

Uma nota sobre pools: A ideia de uma pool é pré-alocar uma determinada quantidade de processos ou threads (no caso de uma Thread Pool) para que fiquem em espera para realizarem alguma tarefa em um tempo futuro. É uma forma de evitar desperdício de tempo com a alocação de um novo processo ou thread, que não é uma operação muito leve no nível do kernel. Além disso, é uma forma de determinar recursos finitos para o número de clientes que se espera ter. Por exemplo, se o servidor tem 8GB de memória, mas só podemos usar 6GB para as pools que receberão as requisições, então as configuramos para consumirem no máximo isso, o que vai limitar o número de processos / threads que elas poderão pré-alocar.

Em um cenário onde há necessidade de alta concorrência para responder à milhares ou dezenas de milhares de conexões, todo esse processo tradicional é muito custoso (gasta-se muito tempo com troca de contexto), muita alocação de memória e a concorrência fica limitada à quantidade de processos que sua máquina consegue manejar. Não obstante, é lento, pois a cada nova requisição todo o código precisa ser inicializado do zero novamente. Mas esse modelo tradicional também tem seus benefícios, a depender do ponto de vista. Por exemplo, é um modelo stateless, o tempo de vida de uma requisição é curto, assim que o resultado é preparado e retornado, tudo é finalizado e retirado da memória, isso diminui as chances de memory leaks.

Abaixo o diagrama de como funciona essa arquitetura tradicional. O Nginx recebe as requisições da Web (normalmente advindas das portas 80 e 443) e então as encaminha para o socket FastCGI do PHP-FPM que maneja as execuções nas pools de worker processes.

Diagrama arquitetura tradicional

O fluxo tradicional com PHP-FPM é mais ou menos assim:

  • Recebe a requisição;
  • Carrega os códigos (processo léxico, de parser, compilação para opcodes etc);
  • Inicializa os objetos e variáveis;
  • Executa o código;
  • Retorna a resposta;
  • Recicla os recursos, liberando o worker process para outra requisição;

Para a maioria das aplicações de uso menos intensivo de I/O, esse modelo é perfeito e estável. Mas quando precisamos de um uso mais intensivo como, por exemplo, para lidar com milhares ou centenas de milhares de conexões simultâneas, é impossível que tenhamos centenas de milhares de processos sendo criados para cuidar dessas requisições. Muito menos poderíamos criar centenas de milhares de threads, dada a limitação do número de threads por processo imposta pelo sistema operacional e também devido ao overhead que isso causaria. Mesmo que fosse possível resolver o problema usando um número menor de threads, trabalhar com threads não é simples, impõe muitos problemas de comunicação e sincronia para lidar.

É nesse ponto que entram as soluções de I/O não bloqueante com multiplexing de I/O. É aqui que ReactPHP, Nginx, NodeJS, Netty, Swoole, Go etc, resolvem o problema, cada um na sua maneira. Mas a forma mais comum e mais utilizada é usando uma abordagem orientada a eventos com o padrão Reactor.

O ciclo de vida de uma requisição em um servidor do Swoole se limita a bem menos etapas, pois depois do first load, ele mantém os recursos em memória:

  • Recebe a requisição;
  • Executa o código;
  • Retorna a resposta;

A arquitetura com Swoole

Em um cenário de alta concorrência e em um modelo de I/O não bloqueante como é o caso do Swoole, ele deixa de lidar apenas com uma requisição bloqueante (como funciona no PHP-FPM) e ganha o poder de lidar com várias requisições ao mesmo tempo, de forma não bloqueante, graças aos reactors.

O Swoole roda em modo CLI e ele forka um determinado número de processos a depender da quantidade de núcleos da sua CPU. Veja um panorama da arquitetura do Swoole:

Camadas arquitetura com Swoole

O padrão Reactor no Swoole é multi-thread e assíncrono, igual comentamos anteriormente, ele faz uso da chamada de sistema epool. O Main Reactor é uma thread, assim como os reactors auxiliares. O Main Reactor é o que fica ouvindo o socket por novas conexões, ele faz balanceamento de carga entre os reactors auxiliares.

  • Master: o processo principal, o processo pai, o que forkará o processo Manager e que criará as threads do Reactor.

  • Manager: processo gerenciador, o que forka e gerencia os processos workers. Os reactors estarão em constante comunicação com o Manager (através do processo Master).

  • Worker: processo de trabalho, onde as tarefas são executadas;

  • Task Worker: processo de trabalho de tarefa assíncrona, é um auxiliador do Worker, ele trabalha principalmente processando tarefas de sincronização de longa data;

O mais importante aqui é entendermos que quando iniciamos um servidor do Swoole, 2 + n + m processos são criados, ou seja, o processo Master, o processo Manager e n refere-se aos processos Workers e m refere-se aos processos Task Workers, sendo que n e m serão relativos à quantidade de núcleos do seu processador. Se o seu processador tiver 6 núcleos, ele forkará 8 processos, sendo 3 Workers e 3 Task Workers.

A árvore de processo da execução de um servidor do Swoole em uma máquina de 6 núcleos:

 | |   \-+= 05535 kennedytedesco php server.php (Master)
 | |     \-+- 05536 kennedytedesco php server.php (Manager)
 | |       |--- 05537 kennedytedesco php server.php (worker / task worker)
 | |       |--- 05538 kennedytedesco php server.php (worker / task worker)
 | |       |--- 05539 kennedytedesco php server.php (worker / task worker)
 | |       |--- 05540 kennedytedesco php server.php (worker / task worker)
 | |       |--- 05541 kennedytedesco php server.php (worker / task worker)
 | |       \--- 05542 kennedytedesco php server.php (worker / task worker)

Quando trabalhamos de forma assíncrona não sabemos em qual tempo futuro a nossa resposta estará pronta e então passamos a usar os famosos callbacks. Com o tempo, é comum termos muitos callbacks aninhados, é aí que chegamos no famoso Callback Hell. Por exemplo, em NodeJS:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

Existem várias formas que diferentes linguagens/frameworks usam para resolver esse problema, como: Promises (recomendo a leitura do artigo de Promises no ReactPHP) , yield/generators, instruções Async / Await (como em C#).

Em Swoole a forma mais elegante e eficiente de resolver esse problema é usando corrotinas. Corrotinas são uma importante parte do núcleo do Swoole.

Um pouco da sintaxe de corrotinas:

<?php

use Swoole\Coroutine\System;

go(static function () {
    System::sleep(1);
    echo 'a';
});

go(static function () {
    System::sleep(2);
    echo 'b';
});

Corrotina (Coroutine)

Programação concorrente é a composição de atividades independentes e o modelo tradicional de se aplicar concorrência é o multi threaded com memória compartilhada. Mas existem muitos desafios nas trocas mensagens, sincronização, sem contar que todas essas operações são custosas a nível de consumo de recursos. E se existisse uma forma mais simples e bem mais leve de se ter concorrência? Sim, existe! As corrotinas se propõem a resolver esse problema.

O uso de concorrência para “ocultar” a latência das operações I/O se faz cada vez mais necessário com aplicativos que necessitam servir dezenas ou centenas de milhares de clientes simultaneamente. Corrotinas são uma forma de se aplicar concorrência, onde valores são passados entre atividades independentes (coroutines). Já os canais (channels) são o mecanismo que uma corrotina tem para se comunicar com outra corrotina (passando valores).

Há o senso de que corrotinas são uma espécie de “threads de peso leve”. Alguns autores chamam corrotinas de “green threads”, pra dar essa ideia de que são leves, consomem menos recursos. Mas isso não pode ser levado no sentido literal, pois corrotinas não são threads. Todas as operações de uma corrotina acontecem no modo do usuário (user mode), não envolve diretamente o kernel (kernel mode) como acontece com as threads, isso faz com que o custo de criação e consumo de recursos aconteça em uma escala muito menor. O paralelo de se comparar corrotinas com threads é útil apenas para que entendamos quando devemos utilizá-las. Basicamente todo caso de uso que teríamos com threads, podemos fazer com corrotinas. O Swoole por padrão cria uma corrotina para cada requisição recebida, esse que também é o padrão em Go. Teremos um artigo focado só em corrotinas.

Desde a versão 4 o Swoole provê um completo suporte à corrotinas, num modelo relativamente parecido com o que se implementa em Go, mas com algumas diferenças internas fundamentais, pois no Swoole uma única corrotina é executada por vez através de um scheduler single threaded. Enquanto em Go o scheduler de corrotinas é multi threaded, ele paraleliza a execução das corrotinas (e internamente usa locks e mutex para controlar a sincronização).

Nota: Scheduling é um mecanismo que atribui tarefas para serem executadas nos workers, é o mecanismo que gerencia qual tarefa será executada em um dado momento, e quem aplica esse mecanismo é o scheduler. Num modelo mais tradicional, pensando no scheduler de um sistema operacional, tarefa seria uma thread e worker seria um núcleo da CPU. No caso do Swoole, tarefa é uma corrotina e worker é um worker process.

Ambos os modelos tem seus prós e contras. O “contra” do modelo do Swoole é que para compartilhar variáveis globais e recursos entre diferentes processos do Swoole, precisa-se pensar um pouco mais, se vai usar IPC (Inter Process Communication) ou outra abordagem (Table, Atomic etc). Mas esse costuma ser um caso de uso bem mais específico. Na maior parte dos casos o desenvolvedor não precisará ter esse tipo de preocupação.

O pró do modelo do Swoole é que para atualizar recursos compartilhados entre corrotinas, não precisamos nos preocupar em implementar locks (pra cuidar do acesso concorrente a um mesmo recurso), uma vez que a execução é single-thread e que as corrotinas são executadas uma por vez. O fato de de o scheduler de corrotinas operar em uma única thread pode dar uma sensação de que ele não é tão eficiente, mas é importante ressaltar que as corrotinas são pausadas/resumidas a partir de eventos de I/O, então a alternância de execução delas pelo scheduler ocorre de maneira muito alta, não dando muito tempo para ociosidade.

Falando sobre custos e as dificuldades de cada abordagem, temos esse comparativo:

Multi-processoMulti-threadCorrotinas
CriaçãoChamada de sistema fork()Pthreads API pthread_create()Função go()
Custo de SchedulingAltoModeradoExtremamente baixo
ConcorrênciaCentenas de processosMilhares de threadsCentenas de milhares de corrotinas
Dificuldade de desenvolvimentoAltaMuito altaBaixa

Executando os primeiros exemplos

O melhor recurso para visualizar alguns exemplos do que é possível fazer com o Swoole é o Swoole by Examples. Clone esse repositório na sua máquina e então execute o comando abaixo para inicializar os containers:

Observação: É necessário que você tenha Docker instalado na sua máquina.

Docker - Fundamentos
Curso de Docker - Fundamentos
CONHEÇA O CURSO
$ docker-compose up -d

O primeiro exemplo que vamos executar trata-se de uma execução síncrona (tradicional):

<?php

(function () {
    sleep(2);
    echo "1";
})();

(function () {
    sleep(1);
    echo "2";
})();  

Execute no terminal:

$ docker-compose exec client bash -c "time ./io/blocking-io.php"

O resultado será:

12
real    0m3.029s
user    0m0.010s
sys 0m0.010s

O script levou cerca de 3 segundos pra executar e retornou 12. A função sleep() foi usada para simular um bloqueio de I/O.

O mesmo exemplo escrito de forma assíncrona usando corrotinas:

<?php

go(function () {
    co::sleep(2);
    echo "1";
});

go(function () {
    co::sleep(1);
    echo "2";
});

Execute:

$ docker-compose exec client bash -c "time ./io/non-blocking-io.php"

O resultado será:

21
real    0m2.033s
user    0m0.020s
sys 0m0.000s

Executou em cerca de dois segundos, ou seja, ele custou o tempo da maior execução para retornar os resultados, ao invés de executar cada uma função de forma bloqueante, o que custaria 3 segundos.

Benchmarking

Usando a ferramenta wrk gerei benchmarking de quatro servidores “hello world” rodando em diferentes plataformas:

1) Servidor embutido do PHP;

2) Servidor em NodeJS;

3) Servidor em Go;

4) Servidor Swoole;

O exemplo do servidor embutido do PHP:

<?php
// vanilla.php

echo "Hello World";

Iniciei o servidor embutido:

$ php -S 127.0.0.1:8101 vanilla.php

E então rodei o benchmarking:

$ wrk -t4 -c200 -d10s http://127.0.0.1:8101

O resultado foi:

Running 10s test @ http://127.0.0.1:8101
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.46ms    9.56ms 141.27ms   98.92%
    Req/Sec     1.41k     1.70k    9.47k    85.45%
  8871 requests in 10.05s, 1.47MB read
  Socket errors: connect 0, read 9275, write 0, timeout 0
Requests/sec:    882.46
Transfer/sec:    149.95KB

Conseguimos 882 requisições por segundo.

No servidor escrito em NodeJS:

const http = require('http');

const server = http.createServer((req, res) => {
    res.end('Hello World')
});

server.listen(8101, '0.0.0.0');

O resultado foi:

Running 10s test @ http://127.0.0.1:8101
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.05ms    1.09ms  43.57ms   98.31%
    Req/Sec    12.49k     1.46k   13.49k    97.03%
  502227 requests in 10.10s, 53.16MB read
  Socket errors: connect 0, read 110, write 0, timeout 0
Requests/sec:  49720.54
Transfer/sec:      5.26MB

Consegui cerca de 50 mil requisições por segundo.

No servidor escrito em Go:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", HelloServer)
    _ = http.ListenAndServe(":8101", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprintf(w, "Hello World")
}

O resultado foi:

Running 10s test @ http://127.0.0.1:8101
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.05ms  624.03us  42.09ms   92.46%
    Req/Sec    47.05k     2.31k   50.59k    95.50%
  1873010 requests in 10.00s, 228.64MB read
  Socket errors: connect 0, read 48, write 0, timeout 0
Requests/sec: 187280.40
Transfer/sec:     22.86MB

Cerca de 188 mil requisições por segundo.

Agora, no servidor do Swoole:

<?php

use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;

$server = new Server('0.0.0.0', 8101);

$server->on('request', static function (Request $request, Response $response) {
    $response->header('Content-Type', 'text/plain');
    $response->end('Hello World');
});

$server->start();

O resultado foi:

Running 10s test @ http://127.0.0.1:8101
  4 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.87ms  660.42us  42.95ms   98.60%
    Req/Sec    48.55k     3.12k   53.67k    88.75%
  1933132 requests in 10.01s, 304.19MB read
  Socket errors: connect 0, read 41, write 0, timeout 0
Requests/sec: 193149.91
Transfer/sec:     30.39MB

Não, você não está vendo errado, foram 193 mil requisições por segundo, um pouco mais do que conseguimos com Go. É um número impressionante.

Esse tipo de benchmarking com exemplos simples e hipotéticos não possuem um valor tão significativo, eles precisam ser relativizados, pois as coisas mudam em uma aplicação real que faz um uso mais intensivo de I/O. Ainda assim, é possível enxergar um espectro do que a tecnologia pode alcançar e também nos ajuda a fazer comparações com outras plataformas, como fizemos com NodeJS e Go.

Considerações finais

Esse artigo teve como objetivo fazer um comparativo entre o modelo tradicional síncrono com o assíncrono não bloqueante orientado a eventos. Também foram passadas as principais ideias por trás do Swoole.

Por fim, é interessante informar que o Swoole permite em sua API que implementemos outros padrões como Thread pool pattern. O Swoole também permite que criemos aplicações multi-process.

Leitura sugerida

Dando continuidade aos estudos de Swoole, sugiro a leitura do artigo sequência: Trabalhando com corrotinas, canais e explorando um pouco mais o scheduler de corrotinas do Swoole

Desenvolvedor PHP
Formação: Desenvolvedor PHP
Nesta formação você aprenderá todos os conceitos da linguagem PHP, uma das mais utilizadas no mercado. Desde de conceitos de base, até características mais avançadas, como orientação a objetos, práticas de mercado, integração com banco de dados. Ao final, você terá conhecimento para desenvolver aplicações PHP usando as práticas mais modernas do mercado.
CONHEÇA A FORMAÇÃO

Google na .NET Foundation? SQL Server para Linux? Visual Studio para MacOS? Saiba como foi o Connect()

A Microsoft realizou no dia 16 de novembro sua tradicional conferência anual, a Connect(). Trata-se de um evento muito importante não somente para os profissionais envolvidos com tecnologias Microsoft, mas sim de um evento que vem ganhando cada vez mais relevância para a comunidade com um todo desde a mudança do comportamento da Microsoft com relação às plataformas Open Source.

A Microsoft utiliza este evento também para apresentar quais são as grandes novidades para desenvolvedores de software e profissionais de infraestrutura. E podemos afirmar que as novidades apresentadas neste ano são simplesmente de deixar o queixo caído!

Se você não está por dentro das novidades, dê uma olhadinha neste post que fizemos para que você fique por dentro de tudo! 😉

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Google entra oficialmente para a .NET Foundation

Aí está algo que era inimaginável há pouco tempo… A Google entrou de forma oficial dentro da .NET Foundation.

Caso você não conheça, a .NET Foundation é o braço open source da Microsoft, responsável por gerenciar e suportar o .NET Framework dentro da comunidade. Ela foi criada desde o momento em que a Microsoft decidiu tornar todo o ecossistema .NET completamente gratuito e open source. Ela seria, fazendo um paralelo com outras plataformas, o equivalente à Apache Foundation ou à Eclipse Foundation.

Já que o Google decidiu se juntar à .NET Foundation, isso quer dizer que ele irá contribuir de maneira oficial e permanente com o desenvolvimento e evolução das tecnologias .NET. Já já teremos então códigos dos engenheiros da Google dentro dos repositórios .NET!

A cooperação Google e Microsoft já não é tão recente. O Angular2, por exemplo, foi escrito em TypeScript, que é um superset do JavaScript criado pela… Microsoft! Houve uma intensa interação entre os times da Google e da Microsoft no processo de criação do Angular2, interação essa que foi muito proveitosa segundo ambos os lados. Mas, a entrada da Google como membro oficial da .NET Foundation expande os horizontes para essa parceria.

Segundo a Google, a intenção de fazer parte da .NET Foundation é melhorar o suporte à aplicações .NET dentro de sua plataforma de computação em nuvem, a Google Cloud. Mas, será que mais coisas estão para surgir dessa parceria inusitada?

Microsoft entra para a Linux Foundation

Se a parceria anterior era inusitada, esta aqui era no mínimo completamente inimaginável anteriormente. Mas, aconteceu: a Microsoft entrou como membro oficial da Linux Foundation! Isso quer dizer que a Microsoft agora se compromete de maneira oficial a contribuir com o desenvolvimento e evolução dos sistemas baseados em Linux. E aí entram todas as distribuições que nós conhecemos ou não, como Ubuntu, Debian e por aí vai.

A Microsoft já adotava o tom de cooperação com sistemas baseados em Linux há algum tempo, cooperação esta que foi se intensificando com o lançamento de softwares para ambientes Linux. Porém, a entrada da Microsoft como membro oficial da Linux Foundation leva tudo a um patamar muito superior. E o melhor: a Linux Foundation já se manifestou de maneira completamente favorável e entusiasmada à entrada da Microsoft.

É cedo para afirmar o que pode acontecer com relação a este acontecimento. Mas, certamente o suporte ao .NET Framework dentro de ambientes não-Windows (suporte que já é muito bom na verdade) tende a melhorar de maneira exponencial. Há também uma grande tendência de começar a surgir soluções interoperáveis entre servidores baseados em Linux e servidores Windows, interoperabilidade essa que existe hoje em dia mas pode ser perfeitamente aperfeiçoada. Vamos aguardar…

Samsung passa a dar suporte oficial para o .NET Framework no Tizen

Outro ponto importantíssimo para o .NET Framework. A Microsoft anunciou uma parceria com a Samsung, o que irá permitir o desenvolvimento de aplicações .NET nativas para dispositivos baseados no Tizen!

O Tizen é um sistema operacional desenvolvido pela Samsung para smartphones, wearable devices e smart TVs. Se você, por exemplo, tem uma smart TV Samsung, a chance de ela estar rodando o Tizen internamente é bem grande.

Você pode estar pensando que o Tizen não é um sistema operacional lá muito famoso e que o impacto disso não é tão grande. Mas, nos permita discordar disso, rs. As smart TVs, por exemplo, estão se tornando cada vez mais populares. A Samsung é uma das líderes de mercado neste segmento. Isso é uma possibilidade e tanto para desenvolvedores .NET começarem a expandir os horizontes de suas aplicações com o mínimo de esforço de reescrita de código. Há também o fato de que é uma oportunidade muito legal para que melhorias no suporte do .NET Framework a dispositivos “menores” possa ser melhorado com base na experiência da implantação de aplicações dentro do Tizen. E ainda há outro ponto: se a Samsung conseguiu dar este suporte, por que outras plataformas mobile futuramente não podem dar suporte ao desenvolvimento .NET nativo? 😉

Se quiser saber mais sobre o anúncio oficial do time do Tizen com relação ao suporte ao .NET, é só clicar aqui

SQL Server para Linux

Pois é, agora podemos rodar o SQL Server no Linux!

Isso não é bem uma novidade… Faz algum tempo que a Microsoft já tinha comentado sobre essa possibilidade, tendo inclusive disponibilizado uma versão do SQL Server para Linux em um beta muito restrito. Porém, agora a versão do SQL Server para Linux (chamado por hora de SQL Server vNext) é pública e qualquer um pode experimentá-la. Ela está aqui.

Para que você fique mais empolgado, o SQL Server agora também é oficialmente suportado pelo Docker! Inclusive, já há imagens oficiais disponíveis no Docker Hub. É só baixar as imagens e começar a se divertir! o/

SQL Server - Desenvolvedor
Curso de SQL Server - Desenvolvedor
CONHEÇA O CURSO

Visual Studio for Mac

Outra novidade que era inimaginável há pouquíssimo tempo atrás… Agora a Microsoft disponibiliza uma versão do Visual Studio para… Mac! E sim, estamos falando de Visual Studio mesmo, aquele igualzinho ao do Windows, e não do Visual Studio Code. Agora será possível ter a experiência do Visual Studio for Windows dentro do MacOS de maneira nativa. Você pode obter o Visual Studio for Mac aqui.

Nós instalamos o Visual Studio for Mac e também testamos alguns de seus recursos. Confira nas imagens abaixo:

Assim que você baixa e monta o arquivo DMG, você cai nesta tela. Basta dar um duplo clique na seta gigante que o processo de instalação será iniciado.

Você receberá o tradicional aviso do MacOS sobre a origem do instalador. Não se preocupe, pode confiar e clicar no “Abrir” ou “Open”.

Como não poderia faltar, os tradicionais termos de licença, haha. Marque o checkbox para afirmar que você concorda com os termos de licença para continuar com o processo de instalação.

Nesta página, você poderá escolher os componentes adicionais a serem instalados junto com o Visual Studio. Perceba que, por estes componentes adicionais, estamos na verdade falando de um único framework: Xamarin! Isso mostra que a Microsoft está cada vez mais absorvendo e integrando o Xamarin (que foi recentemente adquirido pela Microsoft) como um integrante nativo do .NET Framework.

Aqui você pode definir o local de instalação padrão do Visual Studio. Se quiser alterá-lo, basta clicar no botão “+” (sim, o ícone deste botão não ficou muito legal, rs).

Logo depois é apresentado um pequeno review dos componentes a serem instalados.

E agora é esperar pacientemente a instalação. Se você marcou os componentes do Xamarin anteriormente, a instalação levará um tempinho bom. Isso ocorre porque o instalador irá baixar todos os componentes necessários para o desenvolvimento de aplicações Android e iOS, o que inclui todos os SDKs necessários.

Quando a instalação for concluída, você verá a tela abaixo:

Agora é hora de abrir o Visual Studio!

Muito igual ao do Windows, não? Haha

O Visual Studio for Mac na verdade traz um misto da interface para o Windows com a interface de ferramentas de desenvolvimento tradicionais para o MacOS, como o XCode. A intenção da Microsoft foi suavizar a curva de aprendizado da IDE tanto para desenvolvedores acostumados a desenvolver aplicações dentro da plataforma MacOS com o XCode como para desenvolvedores acostumados com o Visual Studio para Windows.

E já dá até para criar uma série de projetos!

Agora, não se esqueça de que esta é a primeira versão, além de ser um preview ainda. O Visual Studio for Mac ainda tem vários bugs, além de não suportar completamente todas as plataformas da maneira como você pode estar esperando (um exemplo é o desenvolvimento de aplicações ASP.NET Core: nesta versão, você ainda não vai ter a experiência igual a que você tem no Visual Studio para Windows, muito pelo contrário, haha). Mas só a possibilidade de podermos desenvolver aplicações multiplataforma nativas com o .NET Core utilizando IDEs também interoperáveis é fantástico! Ah, provavelmente já já deve ser lançada uma versão para Linux também! 😉

Outro ponto importante: não pense que agora o Visual Studio Code será inutilizado dentro do Mac. Eles são ferramentas diferentes. O Visual Studio Code é um editor de código, enquanto o Visual Studio for Mac é uma IDE propriamente dita. Ambos possuem suas respectivas utilidades. Há ainda o fato de o Visual Studio for Mac ainda não suportar legal o desenvolvimento de aplicações ASP.NET Core, sendo melhor desenvolvê-las no Visual Studio Code junto com o grupo DotNET CLI + Yeoman + Gulp + Bower ainda. Isso também quer dizer que, se você fez nosso curso de Introdução ao ASP.NET Core, você não perdeu o que foi aprendido, muito pelo contrário! o/

E agora!?

Agora só nos resta, como desenvolvedores, aproveitar todos os benefícios que estas novidades nos trará. E não são poucos os benefícios, heim? Rs

A Microsoft já suportava o desenvolvimento multiplataforma com o .NET Framework, mas, a tendência é que o framework agora seja cada vez mais refinado com estas novas parcerias. Há também a forte tendência de que o framework evolua cada vez mais para o campo dos dispositivos mobile, smart devices e wearable devices com a parceria com a Samsung e com a Google, o que certamente ampliará mais ainda as possibilidades para desenvolvedores .NET. A entrada na Linux Foundation certamente ajudará a Microsoft a construir um .NET Framework cada vez mais multiplataforma, além de fomentar o surgimento de aplicações e soluções Windows que se encaixam perfeitamente com soluções Linux, e vice-versa.

O futuro para os desenvolvedores .NET parece excelente, não? =)

(Ah, se você estiver curioso para ver como já é possível desenvolver aplicações .NET de maneira nativa em ambientes Linux e MacOS, você pode dar uma olhadinha no nosso curso de Introdução ao ASP.NET Core. E fique ligado: já já estaremos lançando o nosso curso de Docker! o/).

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

© 2004 - 2019 TreinaWeb Tecnologia LTDA - CNPJ: 06.156.637/0001-58 Av. Paulista, 1765, Conj 71 e 72 - Bela Vista - São Paulo - SP - 01311-200