Fibers: concorrência cooperativa no PHP 8.1

1. Introdução às Fibers no PHP 8.1

Fibers são um mecanismo de concorrência cooperativa introduzido no PHP 8.1 que permite pausar e retomar funções de forma não preemptiva. Diferente da concorrência preemptiva (onde o sistema operacional decide quando trocar de contexto), nas Fibers o próprio código cede voluntariamente o controle usando Fiber::suspend().

O principal problema resolvido pelas Fibers é o bloqueio causado por operações de I/O demoradas — leitura de arquivos, requisições HTTP, consultas a bancos de dados — que travam a execução de todo o script. Antes das Fibers, desenvolvedores recorriam a soluções complexas como filas de mensagens, processos paralelos (pcntl_fork) ou bibliotecas externas como ReactPHP e Amp.

As principais diferenças entre Fibers e outros mecanismos:

  • Generators: pausam a execução com yield, mas não possuem pilha de chamadas independente. Uma Fiber mantém seu próprio stack completo, permitindo chamar funções aninhadas e ainda assim ser suspensa.
  • Threads: compartilham memória e exigem sincronização complexa (mutex, semáforos). Fibers rodam em uma única thread do PHP, sem riscos de race conditions em memória compartilhada.
  • Processos: cada processo tem seu próprio espaço de memória, mas a comunicação entre eles é cara. Fibers são leves e compartilham o mesmo espaço de memória de forma segura.

2. Estrutura e Ciclo de Vida de uma Fiber

O ciclo de vida de uma Fiber é composto por quatro estados: pendente (criada, mas não iniciada), executando (rodando ativamente), suspensa (pausada aguardando retomada) e encerrada (finalizou sua execução).

$fiber = new Fiber(function (): void {
    echo "Fiber iniciada\n";
    $valor = Fiber::suspend('pausando...');
    echo "Recebi: $valor\n";
});

echo "Estado inicial: " . ($fiber->isStarted() ? 'iniciada' : 'pendente') . "\n";
$resultado = $fiber->start();
echo "Fiber retornou: $resultado\n";

$fiber->resume('continuando!');
echo "Fiber encerrada: " . ($fiber->isTerminated() ? 'sim' : 'não') . "\n";

Saída:

Estado inicial: pendente
Fiber iniciada
Fiber retornou: pausando...
Recebi: continuando!
Fiber encerrada: sim

A Fiber começa pendente, é executada com start(), suspende-se voluntariamente com suspend(), e é retomada com resume(). O valor passado para resume() é retornado pelo suspend() dentro da Fiber.

3. Comunicação entre Fibers: Passagem de Valores e Exceções

Fibers permitem comunicação bidirecional. O método suspend($value) envia dados para quem chamou resume(), e resume($value) envia dados de volta para a Fiber. Além disso, é possível lançar exceções dentro de uma Fiber usando $fiber->throw().

Exemplo prático: padrão produtor-consumidor com duas Fibers:

$produtor = new Fiber(function () use (&$consumidor): void {
    for ($i = 1; $i <= 5; $i++) {
        echo "Produzindo item $i\n";
        Fiber::suspend($i); // Envia item para o consumidor
    }
    return 'produção finalizada';
});

$consumidor = new Fiber(function () use (&$produtor): void {
    while (!$produtor->isTerminated()) {
        $item = $produtor->resume();
        if ($produtor->isTerminated()) {
            echo "Consumidor recebeu: $item\n";
            break;
        }
        echo "Consumindo item $item\n";
    }
});

$consumidor->start();

Saída:

Produzindo item 1
Consumindo item 1
Produzindo item 2
Consumindo item 2
...
Produzindo item 5
Consumindo item 5
Consumidor recebeu: produção finalizada

Para tratamento de exceções:

$fiber = new Fiber(function (): void {
    try {
        $valor = Fiber::suspend('aguardando');
        echo "Nunca chega aqui\n";
    } catch (\Exception $e) {
        echo "Exceção capturada: " . $e->getMessage() . "\n";
    }
});

$fiber->start();
$fiber->throw(new \Exception('Erro forçado!'));

4. Fibers vs. Generators: Semelhanças e Diferenças Cruciais

Generators e Fibers compartilham a capacidade de pausar e retomar execução, mas diferem em aspectos fundamentais:

Generators:
- Pausam apenas no ponto exato do yield
- Não podem chamar funções que contenham yield (a menos que usem yield from)
- Não possuem pilha de chamadas independente — o contexto é limitado à própria função geradora

Fibers:
- Podem ser suspensas em qualquer profundidade da pilha de chamadas
- Mantêm um stack completo, permitindo que funções aninhadas chamem Fiber::suspend()
- Oferecem comunicação bidirecional com passagem de valores e exceções

// Generator: só pode pausar no próprio escopo
function gerador(): Generator {
    $resultado = yield 'pausa';
    return $resultado;
}

// Fiber: pode pausar dentro de funções aninhadas
function operacaoLonga(): void {
    $dados = Fiber::suspend('buscando...');
    processar($dados);
}

$fiber = new Fiber(function (): void {
    operacaoLonga();
    echo "Fiber finalizada\n";
});

Use Generators para iterações simples e pipelines de dados. Use Fibers quando precisar de concorrência com múltiplos pontos de suspensão e comunicação complexa.

5. Casos de Uso Práticos no Desenvolvimento Web

Requisições HTTP paralelas com cURL + Fiber

function requisicaoHttp(string $url): string {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

    // Simula I/O não bloqueante (em produção, usar curl_multi)
    $resultado = curl_exec($ch);
    curl_close($ch);

    return $resultado;
}

$urls = [
    'https://api.example.com/dados1',
    'https://api.example.com/dados2',
    'https://api.example.com/dados3',
];

$fibers = [];
foreach ($urls as $url) {
    $fibers[] = new Fiber(function () use ($url): string {
        return requisicaoHttp($url);
    });
}

// Inicia todas as Fibers
foreach ($fibers as $fiber) {
    $fiber->start();
}

// Coleta os resultados
$resultados = [];
foreach ($fibers as $fiber) {
    $resultados[] = $fiber->resume();
}

print_r($resultados);

Servidor HTTP simples com Fibers

$server = stream_socket_server('tcp://127.0.0.1:8080');

while ($conn = stream_socket_accept($server, -1)) {
    $fiber = new Fiber(function () use ($conn): void {
        $request = fread($conn, 1024);

        // Processamento simulado
        Fiber::suspend(); // Cede controle para aceitar nova conexão

        $response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, Fiber!";
        fwrite($conn, $response);
        fclose($conn);
    });

    $fiber->start();
}

6. Limitações e Boas Práticas com Fibers

Fibers não são threads. Elas rodam em uma única thread do PHP, portanto:
- Operações bloqueantes reais (sleep(), file_get_contents() síncrono) ainda bloqueiam todas as Fibers
- Não há paralelismo real — apenas concorrência cooperativa
- Compartilhamento de memória é seguro, mas cuidado com variáveis globais e estáticas

Boas práticas:

// EVITAR: variável global compartilhada
$contadorGlobal = 0;

function incrementar(): void {
    global $contadorGlobal;
    $contadorGlobal++; // Race condition potencial
}

// PREFERIR: encapsulamento e imutabilidade
function processarItem(int $id): string {
    return "Processado: $id";
}

$fiber = new Fiber(function (int $id): string {
    return processarItem($id);
});

Nunca misture Fibers com operações bloqueantes reais. Para I/O verdadeiramente não bloqueante, utilize extensões como ext-curl com curl_multi_* ou bibliotecas como Amp e ReactPHP.

7. Integração com Frameworks e Bibliotecas

Frameworks modernos estão começando a explorar Fibers. O Laravel, por exemplo, pode usar Fibers para implementar tarefas assíncronas em filas. Já o Symfony oferece suporte experimental via Symfony Scheduler.

Para integração com PSR-18 (HTTP Client), é possível criar um cliente que use Fibers para fazer múltiplas requisições simultâneas:

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class FiberHttpClient implements ClientInterface
{
    public function sendRequest(RequestInterface $request): ResponseInterface
    {
        $fiber = new Fiber(function () use ($request): ResponseInterface {
            // Lógica de envio HTTP com Fiber
            Fiber::suspend();
            return new Response(200, [], 'Resposta simulada');
        });

        return $fiber->start();
    }
}

Com Amp ou ReactPHP, Fibers podem substituir callbacks e Promises, tornando o código mais linear:

// Com ReactPHP + Fibers
$loop = React\EventLoop\Loop::get();

$fiber = new Fiber(function () use ($loop): void {
    $resultado = Fiber::suspend(); // Aguarda evento do loop
    echo "Resultado: $resultado\n";
});

$loop->addTimer(1, function () use ($fiber): void {
    $fiber->resume('Timer disparado!');
});

$fiber->start();
$loop->run();

8. Conclusão e Futuro da Concorrência no PHP

Fibers representam um avanço significativo para a concorrência no PHP. Elas permitem escrever código assíncrono com aparência síncrona, eliminando a complexidade de callbacks aninhados e Promises encadeadas. O desenvolvedor mantém o fluxo linear tradicional do PHP, enquanto ganha a capacidade de pausar e retomar operações.

Comparado com outras linguagens:
- Go goroutines: são mais leves e gerenciadas pelo runtime, mas Fibers oferecem controle explícito similar
- Python asyncio: usa async/await com event loop; Fibers são mais flexíveis por não exigirem sintaxe especial

O futuro da concorrência no PHP aponta para:
- Suporte nativo a async/await (já em discussão para futuras versões)
- Maior integração com event loops e I/O não bloqueante
- Frameworks adotando Fibers como mecanismo padrão para tarefas assíncronas

Fibers não substituem soluções maduras como ReactPHP ou Amp, mas oferecem uma base nativa poderosa para construir concorrência de forma simples e elegante.


Referências


Este artigo foi publicado originalmente em seu-site.com.


Glossário

Termo Definição
Concorrência cooperativa Modelo onde as tarefas cedem voluntariamente o controle para outras tarefas
Concorrência preemptiva Modelo onde o sistema operacional decide quando interromper e retomar tarefas
Fiber Unidade de execução leve com pilha de chamadas própria, suspensa e retomada manualmente
Generator Função que pode pausar e retornar valores com yield, mantendo estado entre iterações
Event Loop Laço principal que gerencia eventos e I/O não bloqueante em bibliotecas como ReactPHP
Async/Await Sintaxe para escrever código assíncrono de forma síncrona (ex: async function em JavaScript)

Perguntas Frequentes (FAQ)

1. Fibers substituem threads no PHP?

Não. Fibers rodam em uma única thread e não oferecem paralelismo real. Para tarefas CPU-intensive, threads (via pthreads ou parallel) ainda são necessárias.

2. Posso usar Fibers com frameworks existentes?

Sim, mas com cuidado. Frameworks como Laravel e Symfony estão gradualmente adicionando suporte. Para integração imediata, use bibliotecas como Amp ou ReactPHP que já abstraem Fibers.

3. Fibers são seguras para produção?

Sim, desde que usadas corretamente. Evite variáveis globais compartilhadas e operações bloqueantes reais dentro de Fibers. Teste exaustivamente em cenários de concorrência.

4. Qual a diferença entre Fiber::suspend() e yield?

Fiber::suspend() pausa a execução da Fiber e retorna um valor para o chamador. yield em Generators também pausa, mas sem preservar uma pilha de chamadas completa.

5. Fibers consomem muita memória?

Cada Fiber mantém sua própria pilha de chamadas, o que consome mais memória que Generators, mas muito menos que threads. Para centenas de Fibers simultâneas, o consumo é aceitável.


Checklist de Implementação

Antes de adotar Fibers em seu projeto, verifique:

  • [ ] PHP 8.1 ou superior instalado
  • [ ] Operações I/O são verdadeiramente não bloqueantes (ex: curl_multi_*, stream_select)
  • [ ] Variáveis globais e estáticas são evitadas dentro de Fibers
  • [ ] Tratamento de exceções entre Fibers está implementado
  • [ ] Recursos (conexões, arquivos) são fechados após uso
  • [ ] Testes de concorrência foram realizados em ambiente de staging

Exemplo Completo: Download Paralelo de Arquivos

function downloadArquivo(string $url, string $destino): int {
    $ch = curl_init($url);
    $fp = fopen($destino, 'w');
    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_exec($ch);
    $tamanho = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD);
    curl_close($ch);
    fclose($fp);
    return $tamanho;
}

$arquivos = [
    'https://exemplo.com/arquivo1.zip' => '/tmp/arquivo1.zip',
    'https://exemplo.com/arquivo2.zip' => '/tmp/arquivo2.zip',
    'https://exemplo.com/arquivo3.zip' => '/tmp/arquivo3.zip',
];

$fibers = [];
foreach ($arquivos as $url => $destino) {
    $fibers[] = new Fiber(function () use ($url, $destino): int {
        return downloadArquivo($url, $destino);
    });
}

// Inicia todos os downloads em paralelo
foreach ($fibers as $fiber) {
    $fiber->start();
}

// Aguarda e coleta resultados
$totalDownloaded = 0;
foreach ($fibers as $fiber) {
    $totalDownloaded += $fiber->resume();
}

echo "Total baixado: $totalDownloaded bytes\n";

Considerações Finais

Fibers no PHP 8.1 não são uma bala de prata, mas representam o passo mais importante em direção à concorrência moderna na linguagem. Elas preenchem uma lacuna histórica entre o PHP procedural tradicional e as demandas atuais por aplicações mais responsivas e eficientes.

O ecossistema PHP está evoluindo rapidamente. Com Fibers como base nativa, bibliotecas e frameworks podem oferecer abstrações mais simples e poderosas para concorrência. O futuro promete suporte nativo a async/await, integração mais profunda com event loops e, quem sabe, até Fibers com paralelismo real via workers.

Comece pequeno: substitua um loop síncrono de requisições HTTP por Fibers. Experimente com um servidor simples. À medida que a comunidade adota e amadurece o uso dessa feature, o PHP se consolida como uma plataforma competitiva para aplicações de alto desempenho.


Artigo escrito em [data]. Última atualização: [data].