[{"data":1,"prerenderedAt":406},["ShallowReactive",2],{"blog-php8-fibers-async":3},{"id":4,"title":5,"body":6,"date":393,"description":24,"excerpt":394,"extension":395,"meta":396,"navigation":65,"path":397,"seo":398,"stem":399,"tags":400,"__hash__":405},"blog\u002Fblog\u002Fphp8-fibers-async.md","PHP 8.1 Fibers: Writing Cooperative Concurrency Without a Framework",{"type":7,"value":8,"toc":386},"minimark",[9,14,18,85,105,109,112,241,244,248,337,341,348,351,355,379,382],[10,11,13],"h2",{"id":12},"what-fibers-actually-are","What Fibers Actually Are",[15,16,17],"p",{},"A Fiber is a stackful coroutine — a function that can suspend its own execution and yield control back to the caller, then be resumed from exactly where it stopped. No OS threads. No shared memory. Full control of the scheduler.",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-php shiki shiki-themes github-light github-dark","$fiber = new Fiber(function(): string {\n    $value = Fiber::suspend('first suspension');\n    echo \"Resumed with: {$value}\\n\";\n    return 'fiber complete';\n});\n\n$firstValue  = $fiber->start();          \u002F\u002F returns 'first suspension'\n$returnValue = $fiber->resume('hello');  \u002F\u002F prints \"Resumed with: hello\"\n                                         \u002F\u002F $returnValue === 'fiber complete'\n","php","",[26,27,28,36,42,48,54,60,67,73,79],"code",{"__ignoreMap":24},[29,30,33],"span",{"class":31,"line":32},"line",1,[29,34,35],{},"$fiber = new Fiber(function(): string {\n",[29,37,39],{"class":31,"line":38},2,[29,40,41],{},"    $value = Fiber::suspend('first suspension');\n",[29,43,45],{"class":31,"line":44},3,[29,46,47],{},"    echo \"Resumed with: {$value}\\n\";\n",[29,49,51],{"class":31,"line":50},4,[29,52,53],{},"    return 'fiber complete';\n",[29,55,57],{"class":31,"line":56},5,[29,58,59],{},"});\n",[29,61,63],{"class":31,"line":62},6,[29,64,66],{"emptyLinePlaceholder":65},true,"\n",[29,68,70],{"class":31,"line":69},7,[29,71,72],{},"$firstValue  = $fiber->start();          \u002F\u002F returns 'first suspension'\n",[29,74,76],{"class":31,"line":75},8,[29,77,78],{},"$returnValue = $fiber->resume('hello');  \u002F\u002F prints \"Resumed with: hello\"\n",[29,80,82],{"class":31,"line":81},9,[29,83,84],{},"                                         \u002F\u002F $returnValue === 'fiber complete'\n",[15,86,87,88,91,92,96,97,100,101,104],{},"The key insight: ",[26,89,90],{},"Fiber::suspend()"," is called from ",[93,94,95],"em",{},"inside"," the fiber, not outside it. The caller gets back whatever was passed to ",[26,98,99],{},"suspend()"," and can send data back via ",[26,102,103],{},"resume()",".",[10,106,108],{"id":107},"building-a-simple-cooperative-scheduler","Building a Simple Cooperative Scheduler",[15,110,111],{},"The real power emerges when you run multiple fibers through a scheduler loop:",[19,113,115],{"className":21,"code":114,"language":23,"meta":24,"style":24},"class Scheduler\n{\n    private array $fibers = [];\n\n    public function add(Fiber $fiber): void\n    {\n        $this->fibers[] = $fiber;\n    }\n\n    public function run(): void\n    {\n        while ($this->fibers) {\n            foreach ($this->fibers as $key => $fiber) {\n                if (!$fiber->isStarted())       $fiber->start();\n                elseif ($fiber->isSuspended())  $fiber->resume();\n\n                if ($fiber->isTerminated()) {\n                    unset($this->fibers[$key]);\n                }\n            }\n        }\n    }\n}\n",[26,116,117,122,127,132,136,141,146,151,156,160,166,171,177,183,189,195,200,206,212,218,224,230,235],{"__ignoreMap":24},[29,118,119],{"class":31,"line":32},[29,120,121],{},"class Scheduler\n",[29,123,124],{"class":31,"line":38},[29,125,126],{},"{\n",[29,128,129],{"class":31,"line":44},[29,130,131],{},"    private array $fibers = [];\n",[29,133,134],{"class":31,"line":50},[29,135,66],{"emptyLinePlaceholder":65},[29,137,138],{"class":31,"line":56},[29,139,140],{},"    public function add(Fiber $fiber): void\n",[29,142,143],{"class":31,"line":62},[29,144,145],{},"    {\n",[29,147,148],{"class":31,"line":69},[29,149,150],{},"        $this->fibers[] = $fiber;\n",[29,152,153],{"class":31,"line":75},[29,154,155],{},"    }\n",[29,157,158],{"class":31,"line":81},[29,159,66],{"emptyLinePlaceholder":65},[29,161,163],{"class":31,"line":162},10,[29,164,165],{},"    public function run(): void\n",[29,167,169],{"class":31,"line":168},11,[29,170,145],{},[29,172,174],{"class":31,"line":173},12,[29,175,176],{},"        while ($this->fibers) {\n",[29,178,180],{"class":31,"line":179},13,[29,181,182],{},"            foreach ($this->fibers as $key => $fiber) {\n",[29,184,186],{"class":31,"line":185},14,[29,187,188],{},"                if (!$fiber->isStarted())       $fiber->start();\n",[29,190,192],{"class":31,"line":191},15,[29,193,194],{},"                elseif ($fiber->isSuspended())  $fiber->resume();\n",[29,196,198],{"class":31,"line":197},16,[29,199,66],{"emptyLinePlaceholder":65},[29,201,203],{"class":31,"line":202},17,[29,204,205],{},"                if ($fiber->isTerminated()) {\n",[29,207,209],{"class":31,"line":208},18,[29,210,211],{},"                    unset($this->fibers[$key]);\n",[29,213,215],{"class":31,"line":214},19,[29,216,217],{},"                }\n",[29,219,221],{"class":31,"line":220},20,[29,222,223],{},"            }\n",[29,225,227],{"class":31,"line":226},21,[29,228,229],{},"        }\n",[29,231,233],{"class":31,"line":232},22,[29,234,155],{},[29,236,238],{"class":31,"line":237},23,[29,239,240],{},"}\n",[15,242,243],{},"Each iteration of the outer loop ticks all registered fibers once. When a fiber suspends, the scheduler moves on to the next one. This is cooperative multitasking — no OS involvement, fully deterministic.",[10,245,247],{"id":246},"practical-use-case-non-blocking-io-simulation","Practical Use Case: Non-Blocking I\u002FO Simulation",[19,249,251],{"className":21,"code":250,"language":23,"meta":24,"style":24},"function fetchUrl(string $url): Generator\n{\n    \u002F\u002F In a real event loop, this would be non-blocking socket I\u002FO.\n    \u002F\u002F Here we simulate it with a suspend point.\n    Fiber::suspend('waiting');\n    return file_get_contents($url);\n}\n\n$scheduler = new Scheduler();\n\nforeach ($urls as $url) {\n    $scheduler->add(new Fiber(function() use ($url) {\n        $result = yield from fetchUrl($url);\n        echo \"Got \" . strlen($result) . \" bytes from {$url}\\n\";\n    }));\n}\n\n$scheduler->run();\n",[26,252,253,258,262,267,272,277,282,286,290,295,299,304,309,314,319,324,328,332],{"__ignoreMap":24},[29,254,255],{"class":31,"line":32},[29,256,257],{},"function fetchUrl(string $url): Generator\n",[29,259,260],{"class":31,"line":38},[29,261,126],{},[29,263,264],{"class":31,"line":44},[29,265,266],{},"    \u002F\u002F In a real event loop, this would be non-blocking socket I\u002FO.\n",[29,268,269],{"class":31,"line":50},[29,270,271],{},"    \u002F\u002F Here we simulate it with a suspend point.\n",[29,273,274],{"class":31,"line":56},[29,275,276],{},"    Fiber::suspend('waiting');\n",[29,278,279],{"class":31,"line":62},[29,280,281],{},"    return file_get_contents($url);\n",[29,283,284],{"class":31,"line":69},[29,285,240],{},[29,287,288],{"class":31,"line":75},[29,289,66],{"emptyLinePlaceholder":65},[29,291,292],{"class":31,"line":81},[29,293,294],{},"$scheduler = new Scheduler();\n",[29,296,297],{"class":31,"line":162},[29,298,66],{"emptyLinePlaceholder":65},[29,300,301],{"class":31,"line":168},[29,302,303],{},"foreach ($urls as $url) {\n",[29,305,306],{"class":31,"line":173},[29,307,308],{},"    $scheduler->add(new Fiber(function() use ($url) {\n",[29,310,311],{"class":31,"line":179},[29,312,313],{},"        $result = yield from fetchUrl($url);\n",[29,315,316],{"class":31,"line":185},[29,317,318],{},"        echo \"Got \" . strlen($result) . \" bytes from {$url}\\n\";\n",[29,320,321],{"class":31,"line":191},[29,322,323],{},"    }));\n",[29,325,326],{"class":31,"line":197},[29,327,240],{},[29,329,330],{"class":31,"line":202},[29,331,66],{"emptyLinePlaceholder":65},[29,333,334],{"class":31,"line":208},[29,335,336],{},"$scheduler->run();\n",[10,338,340],{"id":339},"the-relationship-to-reactphp-and-amphp","The Relationship to ReactPHP and AMPHP",[15,342,343,344,347],{},"Fibers are the foundation that modern PHP async frameworks build on. Before PHP 8.1, ReactPHP used generators and a complex callback chain. Now both ReactPHP and AMPHP use Fibers as their suspension primitive, giving you async I\u002FO with code that ",[93,345,346],{},"looks"," sequential.",[15,349,350],{},"Understanding Fibers at this level makes frameworks like AMPHP far less magical — you can debug their scheduler, reason about execution order, and write extensions without cargo-culting patterns.",[10,352,354],{"id":353},"what-fibers-are-not","What Fibers Are Not",[356,357,358,367,373],"ul",{},[359,360,361,362,366],"li",{},"They are ",[363,364,365],"strong",{},"not threads"," — no parallelism, no shared-memory races",[359,368,361,369,372],{},[363,370,371],{},"not true async"," — they need an event loop to be useful for I\u002FO",[359,374,361,375,378],{},[363,376,377],{},"not a replacement for queues"," — CPU-bound work still blocks the process",[15,380,381],{},"Use Fibers for I\u002FO multiplexing and cooperative scheduling. Use queues (Laravel Horizon, Beanstalkd) for CPU-bound background work.",[383,384,385],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":24,"searchDepth":38,"depth":38,"links":387},[388,389,390,391,392],{"id":12,"depth":38,"text":13},{"id":107,"depth":38,"text":108},{"id":246,"depth":38,"text":247},{"id":339,"depth":38,"text":340},{"id":353,"depth":38,"text":354},"2024-08-20",null,"md",{},"\u002Fblog\u002Fphp8-fibers-async",{"title":5,"description":24},"blog\u002Fphp8-fibers-async",[401,402,403,404],"PHP","PHP 8.1","Fibers","Concurrency","IHFdqB-3rWfDTaO2x_NrOL-JGFZ-yvzQBgpYdk5KzUI",1779430667061]