Coroutine
webman è sviluppato basato su workerman, quindi webman può utilizzare le caratteristiche delle coroutine di workerman.
Le coroutine supportano tre tipi di driver: Swoole, Swow e Fiber.
Requisiti
- PHP >= 8.1
 - Workerman >= 5.1.0 (
composer require workerman/workerman ~v5.1) - webman-framework >= 2.1 (
composer require workerman/webman-framework ~v2.1) - Estensione swoole o swow installata, oppure installare 
composer require revolt/event-loop(Fiber) - Le coroutine sono disabilitate per impostazione predefinita e richiedono la configurazione manuale di 
eventLoopper essere attivate 
Metodo di attivazione
webman supporta l'attivazione di driver diversi per processi diversi, quindi puoi configurare il driver delle coroutine in config/process.php tramite eventLoop:
return [
    'webman' => [
        'handler' => Http::class,
        'listen' => 'http://0.0.0.0:8787',
        'count' => 1,
        'user' => '',
        'group' => '',
        'reusePort' => false,
        'eventLoop' => '', // vuoto per selezione automatica di Select o Event, non abilita le coroutine
        'context' => [],
        'constructor' => [
            'requestClass' => Request::class,
            'logger' => Log::channel('default'),
            'appPath' => app_path(),
            'publicPath' => public_path()
        ]
    ],
    'my-coroutine' => [
        'handler' => Http::class,
        'listen' => 'http://0.0.0.0:8686',
        'count' => 1,
        'user' => '',
        'group' => '',
        'reusePort' => false,
        // Abilitare le coroutine impostando su Workerman\Events\Swoole::class o Workerman\Events\Swow::class o Workerman\Events\Fiber::class
        'eventLoop' => Workerman\Events\Swoole::class,
        'context' => [],
        'constructor' => [
            'requestClass' => Request::class,
            'logger' => Log::channel('default'),
            'appPath' => app_path(),
            'publicPath' => public_path()
        ]
    ]
    // ... altre configurazioni omesse ...
];
Suggerimento
webman può configurareeventLoopdiverso per processi diversi, il che significa che puoi scegliere di abilitare le coroutine per processi specifici.
Ad esempio, nel sopra indicato, il servizio sulla porta 8787 non ha le coroutine abilitate, mentre il servizio sulla porta 8686 ha le coroutine attivate; combinato con nginx, puoi realizzare una distribuzione mista di coroutine e non coroutine.
Esempio di Coroutine
<?php
namespace app\controller;
use support\Response;
use Workerman\Coroutine;
use Workerman\Timer;
class IndexController
{
    public function index(): Response
    {
        Coroutine::create(function(){
            Timer::sleep(1.5);
            echo "hello coroutine\n";
        });
        return response('hello webman');
    }
}
Quando eventLoop è impostato su Swoole, Swow o Fiber, webman creerà una coroutine per ogni richiesta da eseguire e sarà in grado di continuare a creare nuove coroutine per eseguire codice di business mentre gestisce la richiesta.
Limitazioni delle Coroutine
- Quando utilizzi Swoole o Swow come driver, le coroutine si commutano automaticamente quando si verificano IO bloccanti, consentendo di eseguire codice sincrono in modo asincrono.
 - Quando si utilizza Fiber come driver, in caso di IO bloccante, non ci sarà cambio di coroutine, e il processo entrerà in uno stato di blocco.
 - Quando si usano le coroutine, non è possibile operare simultaneamente su una risorsa comune, come una connessione al database o operazioni su file, poiché ciò potrebbe provocare una competizione di risorse; l'uso corretto è utilizzare un pool di connessioni o un blocco per proteggere la risorsa.
 - Quando si utilizzano le coroutine, non è possibile memorizzare dati di stato relativi alla richiesta in variabili globali o statiche, poiché ciò potrebbe comportare inquinamento dei dati globali; l'uso corretto è utilizzare il 
contextdelle coroutine per memorizzarli. 
Ulteriori Considerazioni
Swow eseguirà automaticamente il hook delle funzioni di blocco di PHP a un livello inferiore, ma poiché questo hook influisce su alcuni comportamenti predefiniti di PHP, potresti riscontrare bug se hai installato Swow senza utilizzarlo.
Pertanto, si consiglia di:
- Se il tuo progetto non utilizza Swow, non installare l'estensione Swow.
 - Se il tuo progetto utilizza Swow, imposta 
eventLoopsuWorkerman\Events\Swow::class. 
Contesto delle Coroutine
Nell'ambiente delle coroutine, è vietato memorizzare informazioni di stato relative alla richiesta in variabili globali o statiche, poiché ciò potrebbe portare a inquinamento delle variabili globali, ad esempio:
<?php
namespace app\controller;
use support\Request;
use Workerman\Timer;
class TestController
{
    protected static $name = '';
    public function index(Request $request)
    {
        static::$name = $request->get('name');
        Timer::sleep(5);
        return static::$name;
    }
}
Attenzione
Non è vietato utilizzare variabili globali o statiche in un ambiente di coroutine, ma è vietato utilizzare variabili globali o statiche per memorizzare dati di stato relativi alla richiesta.
Ad esempio, dati di oggetti globali come configurazioni globali, connessioni al database o singleton di alcune classi possono essere memorizzati in variabili globali o statiche.
Impostando il numero di processi su 1, quando inviamo due richieste consecutive:
http://127.0.0.1:8787/test?name=lilei
http://127.0.0.1:8787/test?name=hanmeimei
ci aspettiamo che i risultati restituiti dalle due richieste siano rispettivamente lilei e hanmeimei, ma in realtà restituiscono entrambi hanmeimei.
Questo perché la seconda richiesta ha sovrascritto la variabile statica $name, e quando la prima richiesta termina il suo "sleep" restituendo $name, ora è già diventato hanmeimei.
Il metodo corretto è utilizzare il context per memorizzare i dati di stato della richiesta.
<?php
namespace app\controller;
use support\Request;
use support\Context;
use Workerman\Timer;
class TestController
{
    public function index(Request $request)
    {
        Context::set('name', $request->get('name'));
        Timer::sleep(5);
        return Context::get('name');
    }
}
La classe support\Context è utilizzata per memorizzare i dati di contesto delle coroutine, e una volta completata l'esecuzione della coroutine, i dati corrispondenti verranno automaticamente eliminati.
Nell'ambiente delle coroutine, poiché ogni richiesta è una coroutine separata, i dati nel contesto vengono automaticamente distrutti al termine della richiesta.
Nell'ambiente non coroutine, il contesto viene automaticamente distrutto al termine della richiesta.
Le variabili locali non causeranno inquinamento dei dati
<?php
namespace app\controller;
use support\Request;
use support\Context;
use Workerman\Timer;
class TestController
{
    public function index(Request $request)
    {
        $name = $request->get('name');
        Timer::sleep(5);
        return $name;
    }
}
Poiché $name è una variabile locale, le coroutine non possono accedere alle variabili locali dell'altra, quindi l'uso di variabili locali è sicuro nelle coroutine.
Locker
A volte alcuni componenti o funzioni non considerano l'ambiente delle coroutine, il che può portare a problemi di competizione di risorse o atomicità; in questo caso, puoi utilizzare Workerman\Locker per implementare un locking, in modo da gestire le richieste in coda e prevenire problemi di concorrenza.
<?php
namespace app\controller;
use Redis;
use support\Response;
use Workerman\Coroutine\Locker;
class IndexController
{
    public function index(): Response
    {
        static $redis;
        if (!$redis) {
            $redis = new Redis();
            $redis->connect('127.0.0.1', 6379);
        }
        // Se non si utilizza un lock, in Swoole si potrebbe innescare un errore simile a "Socket#10 has already been bound to another coroutine#10"
        // In Swow, potrebbe innescare un coredump
        // In Fiber, poiché l'estensione Redis è IO bloccante sincrono, quindi non avrà problemi
        Locker::lock('redis');
        $time = $redis->time();
        Locker::unlock('redis');
        return json($time);
    }
}
Esecuzione in parallelo
Quando abbiamo bisogno di eseguire più compiti in parallelo e ottenere risultati, possiamo utilizzare Workerman\Parallel per farlo.
<?php
namespace app\controller;
use support\Response;
use Workerman\Coroutine\Parallel;
class IndexController
{
    public function index(): Response
    {
        $parallel = new Parallel();
        for ($i=1; $i<5; $i++) {
            $parallel->add(function () use ($i) {
                // Fai qualcosa
                return $i;
            });
        }
        $results = $parallel->wait();
        return json($results); // Risposta: [1,2,3,4]
    }
}
Pool - Pool di connessione
Condividere la stessa connessione tra più coroutine può portare a confusione nei dati, quindi è necessario utilizzare un pool di connessione per gestire risorse di connessione a database, redis, ecc.
webman ha già fornito componenti come webman/database, webman/redis, webman/cache, webman/think-orm, webman/think-cache; tutti questi integrano un pool di connessioni e supportano l'uso in ambienti con e senza coroutine.
Se desideri modificare un componente che non ha un pool di connessioni, puoi utilizzare Workerman\Pool per implementarlo. Consulta il seguente codice.
Componente Database
<?php
namespace app;
use Workerman\Coroutine\Context;
use Workerman\Coroutine;
use Workerman\Coroutine\Pool;
class Db
{
    private static ?Pool $pool = null;
    public static function __callStatic($name, $arguments)
    {
        if (self::$pool === null) {
            self::initializePool();
        }
        // Ottieni la connessione dal contesto delle coroutine, garantendo che la stessa coroutine utilizzi la stessa connessione
        $pdo = Context::get('pdo');
        if (!$pdo) {
            // Ottieni la connessione dal pool di connessioni
            $pdo = self::$pool->get();
            Context::set('pdo', $pdo);
            // Quando la coroutine termina, restituisci automaticamente la connessione
            Coroutine::defer(function () use ($pdo) {
                self::$pool->put($pdo);
            });
        }
        return call_user_func_array([$pdo, $name], $arguments);
    }
    private static function initializePool(): void
    {
        // Crea un pool di connessioni, con un numero massimo di 10 connessioni
        self::$pool = new Pool(10);
        // Imposta il creatore di connessioni (per semplicità, è stata omessa la lettura del file di configurazione)
        self::$pool->setConnectionCreator(function () {
            return new \PDO('mysql:host=127.0.0.1;dbname=your_database', 'your_username', 'your_password');
        });
        // Imposta il chiudi connessione
        self::$pool->setConnectionCloser(function ($pdo) {
            $pdo = null;
        });
        // Imposta il controllore di heartbeat
        self::$pool->setHeartbeatChecker(function ($pdo) {
            $pdo->query('SELECT 1');
        });
    }
}
Uso
<?php
namespace app\controller;
use support\Response;
use app\Db;
class IndexController
{
    public function index(): Response
    {
        $value = Db::query('SELECT NOW() as now')->fetchAll();
        return json($value); // [{"now":"2025-02-06 23:41:03","0":"2025-02-06 23:41:03"}]
    }
}
Maggiori informazioni sulle Coroutine e Componenti Correlati
Consulta documentazione delle coroutine di workerman
Distribuzione mista di Coroutine e Non Coroutine
webman supporta la distribuzione mista di coroutine e non coroutine, ad esempio, elaborando business normali senza coroutine e gestendo business di IO lento tramite coroutine, inoltrando le richieste a diversi servizi tramite nginx.
Ad esempio, in config/process.php
return [
    'webman' => [
        'handler' => Http::class,
        'listen' => 'http://0.0.0.0:8787',
        'count' => 1,
        'user' => '',
        'group' => '',
        'reusePort' => false,
        'eventLoop' => '', // vuoto per selezione automatica di Select o Event, non abilita le coroutine
        'context' => [],
        'constructor' => [
            'requestClass' => Request::class,
            'logger' => Log::channel('default'),
            'appPath' => app_path(),
            'publicPath' => public_path()
        ]
    ],
    'my-coroutine' => [
        'handler' => Http::class,
        'listen' => 'http://0.0.0.0:8686',
        'count' => 1,
        'user' => '',
        'group' => '',
        'reusePort' => false,
        // Abilitare le coroutine impostando su Workerman\Events\Swoole::class o Workerman\Events\Swow::class o Workerman\Events\Fiber::class
        'eventLoop' => Workerman\Events\Swoole::class,
        'context' => [],
        'constructor' => [
            'requestClass' => Request::class,
            'logger' => Log::channel('default'),
            'appPath' => app_path(),
            'publicPath' => public_path()
        ]
    ],
    // ... altre configurazioni omesse ...
];
Poi utilizza la configurazione di nginx per inoltrare le richieste ai diversi servizi
upstream webman {
    server 127.0.0.1:8787;
    keepalive 10240;
}
# Aggiungi un nuovo upstream 8686
upstream task {
   server 127.0.0.1:8686;
   keepalive 10240;
}
server {
  server_name webman.com;
  listen 80;
  access_log off;
  root /path/webman/public;
  # Le richieste che iniziano con /tast vanno sulla porta 8686, sostituisci /tast con il prefisso di cui hai bisogno
  location /tast {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      proxy_pass http://task;
  }
  # Altre richieste passano alla porta originale 8787
  location / {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      if (!-f $request_filename){
          proxy_pass http://webman;
      }
  }
}