慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
IT之家
IT之家
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 三生石上(FineUI控件)
Microsoft Azure Blog
Microsoft Azure Blog
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
C
Cyber Attacks, Cyber Crime and Cyber Security
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
WordPress大学
WordPress大学
C
CXSECURITY Database RSS Feed - CXSecurity.com
F
Fox-IT International blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
F
Future of Privacy Forum
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recorded Future
Recorded Future
P
Proofpoint News Feed
O
OpenAI News
C
CERT Recently Published Vulnerability Notes
E
Exploit-DB.com RSS Feed
Spread Privacy
Spread Privacy
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
罗磊的独立博客
V
V2EX - 技术
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
N
Netflix TechBlog - Medium
AWS News Blog
AWS News Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
李成银的技术随笔
C
Cisco Blogs
SecWiki News
SecWiki News
Application and Cybersecurity Blog
Application and Cybersecurity Blog
L
LINUX DO - 热门话题
B
Blog RSS Feed
Google DeepMind News
Google DeepMind News
G
Google Developers Blog
Latest news
Latest news
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks

DEV Community

My agent could see the dropdown. It just couldn't pick anything. Why `mixed` Is the Worst Type in Your PHP Codebase (and How to Kill It) PHP 8.4 Asymmetric Visibility: 5 Patterns That Replace Constructors and Setters apt-mark hold doesn't pin versions — how it nearly removed OpenSSH across our fleet Getting Started with AWS — A Beginner Friendly Introduction I Built a Free Metal Weight Calculator — Here's the Math Behind It From Half Baked Repos to GitHub Glory: How I Am Finishing My Ambitious Ten App Masterpiece Aasa: The Phone That Finally Notices Why Fast Development Fails Without Strong Engineering Foundations Journey Builder vs Automation Studio: Which Tool for Which Job Dynamic Content Blocks: One Email, Different Content Per Tier Everyone's Talking About Gemini 3.5 Flash. The Real Story at Google I/O 2026 Was a Skill File. Enhancing the AI Blog System: SQLite Support and Streamlined Publishing Features I Fine-Tuned Gemma 4 on an Emotion Dataset Using a Single GPU Omnichannel inventory in D365: DOM + the Inventory Add-in File-Drop Automations: SFMC Pattern for Daily Imports Regression Testing in Agile: How to Test Without Slowing Down Your Sprints I build projects and manage teams without a single call Making a Calculator UI with HTML5 and CSS3 Full Next.js + Node.js + PostgreSQL Interview Task Setup Google’s Gemini Coding Demos Revealed the Slow Death of “Blank Page Programming” Verification Activity: SFMC Guard Against Empty Files Integrating Shopify with external systems: MVP connection choices Beyond RAG: Architecting Local Long-Context Pipelines with Gemma 4's 31B Dense Model KloudAudit vs AWS Cost Explorer: Why I Stopped Using Cost Explorer for Waste Detection Telegram: API bot access token Gemma 4 at the Edge AasPass: A lightweight, local-first password vault for developers Why Local AI Was the Real Winner of Google I/O 2026 (An Insider’s Take) Laravel Google Drive Filesystem: Unlimited Cloud Storage with Familiar Syntax When not to build an AI agent (and what to ship instead) What a real Sanity CMS development services proposal looks like Why hybrid search is the boring default we keep recommending I kept improving my .NET order pipeline after a CTO left feedback. Here is where it ended up. Why Developers go behind Linux ? Does Front End need HTML, CSS? - Part - 2 From Prompts to Action: What Gemini 3.5 Flash and the Agentic Stack Mean for Developers Does Front End need HTML, CSS? - Part - 1 The real attack surface for AI coding agents is the config file Chai aur SQL — A Beginner's Journey into Databases Find Your Route Source Score: Continuing Exploration of LLM Usage in Automated Workflows Tried using the Claude Platform on AWS Your Node.js Server is Using Just One CPU. Here's How to Fix It. 🚀 Google Antigravity 2.0 Quietly Changes What It Means to Be a Software Engineer Environment variables vs connection references in Power Platform Multi-BU D365 environment: single tenant, multiple LEs AI API Integration Testing Checklist for Multi-Model Apps ORA-00203 오류 원인과 해결 방법 완벽 가이드 Designing a Data Extension in SFMC: The Four Decisions First
PHP 纤程于生产:四例实情,其胜 curl_multi 与队列
Gabriel Anha · 2026-05-24 · via DEV Community

十一月,PHP 8.1 运输纤维。RFC(请求评论)其小也,API有四法,而 Laravel之代码库多未触及之。启之。vendor/ 新启 Laravel 之项目,则见 Guzzle、Symfony HttpClient 及 ReactPHP 皆暗用之。尔之自码乎?恐无之。

此于 CRUD 之应用,尚可。一旦尔之 HTTP 服务广布五重,流送多吉字节之文件,或织 webhook 之调度,则队列或阻塞之别,渐显其费。Fibers 治一特定之困,而队列过解之。curl_multi未解之解。兹列四例,以证其功。

何谓纤维(六十秒心智模型)

纤丝者,协程也。非线程,非进程。一栈执行,可自暂停,后时复续,尽在相同之PHP进程中。

通篇之API:

$fiber = new Fiber(function (): void {
    echo "step 1\n";
    $resumed = Fiber::suspend("paused");  // hands control back
    echo "step 2 with: $resumed\n";
});

$out = $fiber->start();           // "step 1", returns "paused"
$fiber->resume("hello");          // "step 2 with: hello"
$fiber->isTerminated();           // true

入全景模式 出全屏模式

Fiber::suspend()者,乃玄妙之术也。惟可自运行之Fiber中唤之。若于主流中呼之,则得Fiber\FiberError: Cannot call Fiber::suspend() outside of a Fiber。呼start()resume()者,得回所悬之物。

行此二者,方可行远:

  1. Fibers乃协契之物。无物可夺其志。若Fiber不呼suspend(),行至终而阻主流。
  2. 其流程相同。无全局解释器锁,无内存复制,无进程间通信。全局变量共享。PDO连接亦然,此乃多数人初遇之陷阱(详下)。

例一:并行HTTP分叉

汝之结账接口,呼支付网关,诈欺之务,忠诚之API,运费计算器,及仓廪可用之服务。五呼,每约四百毫秒,次第而行。此乃乐途上之二秒也。

curl_multi_exec()解此困,然此API乃二十年之轮询循环也。do {} whileselect风姿翩翩,凝神观之。其效甚佳。然览之,未得欣悦。

此乃纤丝之版本也。其法:每HTTP调用,皆居于其独纤丝中。一微调度器,泵之。curl_multi而复其纤缕,若柄之终也。

final class ParallelHttp
{
    /** @param array<string, string> $urls keyed by label */
    public static function get(array $urls): array
    {
        $mh = curl_multi_init();
        $handles = [];
        $fibers = [];

        foreach ($urls as $key => $url) {
            $fibers[$key] = new Fiber(function () use ($url, $mh, &$handles, $key) {
                $ch = curl_init($url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_TIMEOUT, 5);
                curl_multi_add_handle($mh, $ch);
                $handles[$key] = $ch;

                // hand control back to the scheduler until curl says done
                Fiber::suspend();

                $body = curl_multi_getcontent($ch);
                curl_multi_remove_handle($mh, $ch);
                curl_close($ch);
                return $body;
            });
            $fibers[$key]->start();
        }

        // pump until every handle is finished
        do {
            curl_multi_exec($mh, $active);
            curl_multi_select($mh, 0.05);
        } while ($active > 0);

        curl_multi_close($mh);

        $results = [];
        foreach ($fibers as $key => $fiber) {
            $results[$key] = $fiber->resume();
        }
        return $results;
    }
}

$out = ParallelHttp::get([
    'payment'  => 'https://api.example.com/payment/check',
    'fraud'    => 'https://api.example.com/fraud/score',
    'loyalty'  => 'https://api.example.com/loyalty/balance',
    'shipping' => 'https://api.example.com/shipping/quote',
    'stock'    => 'https://api.example.com/warehouse/stock',
]);

入全景模式 出全屏模式

一合成基准测试,对五端点施加人工400毫秒服务器端延迟,得序贯cURL耗时约2050毫秒;纤网+curl_multi版本毕,约四百四十毫秒。此乃所期之算。实际用时,不过最慢调用加之少许调度之耗。Guzzle异步文档报相似形状及ReactPHP之HTTP 客户端基准测试同列而坐。

纤维版非胜于原始curl_multi其下本同此网络之素。所异者,每光纤得常体之功能耳。无回调,无then()无承诺链。此乃成败处理非简时之要。试/捕之理,恰如所期。

第二案:反向压力之流处理

览十吉字节之CSV而fopenfgetcsv工巧至矣,然批写于迟滞之库,则谬矣。朴拙之脚本,速若PHP之析,而下游之消者,或耗尽其列,或弃之如敝屣。

纤维使生产者可暂停,待消费者落后。此乃有界缓冲之模式:

final class BoundedChannel
{
    /** @var list<array<string, string>> */
    private array $buffer = [];
    private ?Fiber $waitingProducer = null;
    private ?Fiber $waitingConsumer = null;
    private bool $closed = false;

    public function __construct(private readonly int $capacity = 100) {}

    public function send(array $row): void
    {
        while (count($this->buffer) >= $this->capacity) {
            $this->waitingProducer = Fiber::getCurrent();
            Fiber::suspend(); // wait for the consumer to drain
        }
        $this->buffer[] = $row;
        if ($this->waitingConsumer !== null) {
            $c = $this->waitingConsumer;
            $this->waitingConsumer = null;
            $c->resume();
        }
    }

    public function receive(): ?array
    {
        while ($this->buffer === [] && !$this->closed) {
            $this->waitingConsumer = Fiber::getCurrent();
            Fiber::suspend();
        }
        if ($this->buffer === []) return null;
        $row = array_shift($this->buffer);
        if ($this->waitingProducer !== null) {
            $p = $this->waitingProducer;
            $this->waitingProducer = null;
            $p->resume();
        }
        return $row;
    }

    public function close(): void { $this->closed = true; }
}

入全景模式 出全屏模式

一真实之吞咽管道,以通道相接,织生产之纤与消费之纤:

$channel = new BoundedChannel(capacity: 500);

$producer = new Fiber(function () use ($channel): void {
    $fp = fopen('orders-2026.csv', 'r');
    $header = fgetcsv($fp);
    while (($row = fgetcsv($fp)) !== false) {
        $channel->send(array_combine($header, $row));
    }
    fclose($fp);
    $channel->close();
});

$consumer = new Fiber(function () use ($channel): void {
    $batch = [];
    while (($row = $channel->receive()) !== null) {
        $batch[] = $row;
        if (count($batch) === 50) {
            OrderImporter::insertBatch($batch); // blocking DB write
            $batch = [];
        }
    }
    if ($batch !== []) OrderImporter::insertBatch($batch);
});

$producer->start();
$consumer->start();

全屏模式入 全屏模式出

缓冲区满五百行时,生产者自停。消费者清空,生产者复行。内存恒定。此模式之九吉字节CSV,运行间驻留内存恒在百兆以下,盖因缓冲量限五百行。有趣者非速也(数据库为瓶颈),乃脚本无论何速,终不致耗尽内存。fgetcsv阅之。

一物之生,使人懈怠,然物之生,不能自内助之,则遍染其链。纤维则不然,可自内助之,此实为便捷之胜。

案三:易生成器协程

纤维未兴,世人于PHP为异步事,常作此:

function fetchUser(int $id): Generator {
    $raw = yield $http->get("/users/$id");        // a Promise
    $orders = yield $http->get("/orders?u=$id");
    return ['user' => $raw, 'orders' => $orders];
}

入全屏模式 出全屏模式

汝需一"协程运行者",循生成器而行,待每所予之承,复推其果回。此法可行(Recoil、Amp v2重用之, Laravel Octane亦有所用),然其式: Generator恒误。此函数返array类型系统,未尝合乎实情。

纤维解此。同此理,真返类型:

public function fetchUser(int $id): UserBundle
{
    $user = $this->client->get("/users/$id");      // suspends internally
    $orders = $this->client->get("/orders?u=$id");
    return new UserBundle($user, $orders);
}

步入全景模式 退出全景模式

客待网隙,暂止纤线。此法若直书同步之码。PHPStan、Psalm、IDE补全:皆得正型。Amp团队于v3中,周纤线而重撰诸事(发布记),正为此故。ReactPHP已发。react/async 4.x 亦然:await() 乃基于纤维之原语,使应许返回之库若作同步观。

若汝有2019年基于Generator之"异步"助手,拔除之换纤维,常削文件三成至四成,纯为虚仪。行无变改。

案四:于ReactPHP / AMPHP之中,不更全代码基

不须尽力于事件循环,亦可使用Fibers。此乃多数"是否应使用Amp"之问所忽略之点也。

一Symfony Messenger处理器,需分派至三处Webhooks,可于本地引入一感知Fiber之HTTP客户端,以行微细之事await于处理者之中,否则保持原味。

use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use function Amp\async;
use function Amp\Future\awaitAll;

final class DispatchWebhooksHandler
{
    public function __invoke(OrderPlaced $event): void
    {
        $client = HttpClientBuilder::buildDefault();
        $urls = WebhookSubscriptions::for($event->orderId);

        $futures = array_map(
            fn(string $url) => async(fn() =>
                $client->request(new Request($url, 'POST', json_encode($event)))
            ),
            $urls,
        );

        [$errors, $responses] = awaitAll($futures);
        foreach ($errors as $url => $err) {
            FailedWebhook::record($url, $err->getMessage());
        }
    }
}

入全景模式 出全屏模式

async()生一纟纟丝。awaitAll()阻其处理器,俟诸纤丝毕功,然唯此处理器而已。其余 Symfony 之务,照常进行。无全局事件循环。勿言“将整框架转为异步。”

其要在于 Amp'sEventLoop初用而懒初始化,无待而净止。同此。react/async尔得协程工整之法,而未染余码之弊。

纤维无益处处

PDO阻塞。插入PDO::query()纤维内非为异步。纤维阻尽进程,待 libpq 返归。同此。mysqli以默认模式。同此。file_get_contents()于本地文件。同此。sleep().

之工作,如图像缩放、JSON编码二百兆之结构、bcrypt,于纤线无所益。纤线者,为I/O之待也。若瓶颈乃紧for之循环,以事 hashing,则需 pthreads、并行,或pcntl_fork,非 coroutines也。

心验之:此操作于Node.js,当自Promise而返乎?libuv之助而呼耶?若然,则基于纤维之裹缚可助。否(汝之窒碍在PHP本身),纤维则无用矣。

其大注曰:有异步之PDO替者(amphp/postgresamphp/mysql)。者,与纤线协作者也。其存在,当知之。其互换,鲜有免费者。雅言与教义,皆假阻塞驱动为前提。

错误处理之妙谛

纤线内之例外,几若所期。几若。

$fiber = new Fiber(function (): void {
    Fiber::suspend();
    throw new RuntimeException("boom");
});

$fiber->start();
try {
    $fiber->resume(); // exception propagates HERE, not where it was thrown
} catch (RuntimeException $e) {
    // caught
}

全屏模式入 全屏模式出

例外行至所召之者resume()固常无碍。所陷者,Fiber::throw()

$fiber = new Fiber(function (): void {
    try {
        Fiber::suspend();
    } catch (DomainException $e) {
        // the throw() call lands inside the suspend
    }
});
$fiber->start();
$fiber->throw(new DomainException("cancelled"));

Enter fullscreen mode Exit fullscreen mode

throw()suspend()处注入异常。若Fiber不摄之,则异常上涌至调用者。throw(),世人用以取消,然设一例外,越三纤而失其踪,莫知所归。

要诀:纤体宜短。若纤之入口需更精微之错处处理,非止于试/捕,则当返一Result式之象,而非抛之。suspend()

此意于汝代码之基

纤维乃本源,非可外铄之器。当求之境:并行I/O之散,知压之流,易生成器异步之助,及嵌于阻隔之框架内。不当求之境:CPU之劳,PDO,无明悬停之故处。

尔若已运 Laravel 或 Symfony 之应用,其最低风险之始,乃第四案:一独应之器,借 Amp 或 react/async,生数纤维于本处,即出。无全局事件之环。无基建之变。度其胜,乃扩之。

尔今应用,其最缓之散布何在?第四案,无改框架,可应之乎?


若此有益

纤维者,运行时之素也。所重者,架构之问耳:此散逸之势,当存于域层耶,抑或存于适配器耶?此乃众代码之纠葛所由生也。PHP之解耦者,框架之常不足,代码基所求之架构层也;其述如何使异步I/O于边缘,而域码安然同步.

Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework

有Kindle、Paperback、Hardcover版。英文、德文、日文版今已出,葡萄牙文及西班牙文版将即至。