Mastering Sync vs Async in PHP: When to Use Coroutines for Faster Apps

This article explains the difference between synchronous and asynchronous execution in PHP, shows classic blocking code and non‑blocking examples, compares their pros and cons, and introduces Swoole coroutines as a way to achieve lightweight concurrency.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
Mastering Sync vs Async in PHP: When to Use Coroutines for Faster Apps

Overview

In PHP development, distinguishing between synchronous (blocking) and asynchronous (non‑blocking) execution is essential for building performant applications. Synchronous code waits for an operation to finish before proceeding, while asynchronous code continues immediately and receives the result later via callbacks, promises, or events.

Synchronous Execution

A synchronous call blocks the entire request until the called function returns. This model is simple and deterministic, but it can become a bottleneck when many I/O‑bound tasks run concurrently.

Typical PHP Example

<?php
$result = Db::table('user')
    ->where('id', 1)
    ->select(); // pseudo‑code, assume a DB query
sleep(3); // simulate a 3‑second query
echo json_encode($result); // output only after the query finishes
The $result variable is not assigned until select() returns, so the script blocks while waiting for the database.
<?php
$num = -30;
$result = abs($num); // returns immediately
echo json_encode($result); // outputs 30
The abs() function is synchronous; it returns a value instantly.

In a typical web request, the server does not send a response until the whole page is rendered. If rendering includes a long‑running operation such as sending an email, the user experiences a delay.

<?php
// simulate sending an email that takes 30 seconds
$status = sendEmail();
sleep(30);
echo "发送邮件" . ($status ? '完成' : '失败');
Synchronous Pros and Cons Pros: clear control flow, easy debugging, deterministic results. Cons: I/O‑intensive tasks keep the process idle, wasting CPU and memory; under the traditional PHP‑FPM model this amplifies server load.

Asynchronous Execution

Asynchronous code launches an operation and immediately continues with subsequent logic. The result is delivered later through a callback, promise, or event, which improves concurrency for I/O‑bound workloads.

PHP Process Fork Example

<?php
$pid = pcntl_fork();
if ($pid == 0) {
    // Child process: handle email sending
    sleep(30); // simulate long task
    sendEmail();
    exit(0);
}
// Parent process does not wait, continues execution
pcntl_waitpid($pid, $status, WNOHANG); // non‑blocking wait
echo "邮件发送中..."; // immediate output

The parent launches a child process and proceeds without waiting for the child’s result, reducing response time at the cost of added process‑management complexity.

JavaScript demonstrates the same idea with an AJAX call; PHP can achieve comparable behavior using curl_multi or libraries such as Guzzle.

// JavaScript example (PHP equivalent uses curl_multi)
$.ajax({
    url: '/api/data',
    async: true, // default is asynchronous
    success: function(data) {
        console.log(data); // handle result in callback
    }
});
console.log('继续其他任务'); // code continues without blocking
Asynchronous Pros and Cons Pros: higher concurrency, better CPU utilization for I/O‑bound tasks. Cons: callback hell, harder error handling, nondeterministic ordering; requires additional mechanisms to guarantee data consistency.

From Traditional PHP to Coroutines

The traditional PHP model relies on multiple OS processes or threads, which limits scalability. The Swoole extension introduces coroutines , lightweight user‑space threads scheduled by the runtime. A coroutine can suspend itself when it encounters I/O, allowing other coroutines to run without blocking the OS thread.

EasySwoole builds on Swoole’s coroutine support and provides a CSP (Communicating Sequential Processes) model. Key primitives include:

Channel: a thread‑safe queue for passing data between coroutines.

WaitGroup: a counter that blocks the calling coroutine until all spawned coroutines call done().

Atomic: lock‑free integer operations for safe shared‑state updates.

These primitives solve two common problems when launching many coroutines with go():

Ensuring the main coroutine does not exit before child coroutines finish.

Coordinating data sharing and result aggregation across concurrent tasks (e.g., batch database queries).

By using WaitGroup and Channel, developers can write code that looks synchronous while actually executing many I/O operations concurrently.

Coroutine diagram
Coroutine diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendPHPCoroutinesSynchronous
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.