PHpullh
심층 가이드/PHP/비동기/동시성

PHP · 심층 가이드

PHP 비동기/동시성 완전 정리

이벤트 루프가 없는 PHP에서 curl_multi, Fiber, stream_select, 자식 프로세스를 조합해 여러 작업을 겹쳐 실행하는 실전 구조를 단계별로 만들어 봅니다.

주제 14개 · 예제 코드 포함 · 최종 수정 2026-08-30 · 작성 pullh 편집팀

PHP의 실행 모델은 요청 하나가 시작해서 끝나면 메모리를 통째로 버리는 구조입니다. 그래서 언어에 내장된 이벤트 루프가 없고, 표준 함수 대부분은 블로킹으로 동작합니다. Fiber가 들어오면서 상황이 달라졌지만 오해하기 쉬운 부분이 있습니다. Fiber는 중단하고 재개하는 원시 기능만 제공할 뿐 스케줄러도, 논블로킹 I/O도 함께 주지 않습니다. 실제 동시성은 결국 curl_multistream_select 같은 다중화 API, 아니면 별도 프로세스에서 나옵니다.

그래서 순서를 실용적인 쪽부터 잡았습니다. curl_multi 로 병렬 요청은 코드를 거의 바꾸지 않고 대기 시간을 겹치는 가장 빠른 수단이고, curl_multi 배치 API 요청에서 동시 실행 개수를 제한하는 패턴까지 확장합니다. 다음으로 Fiber 기본 사용간단한 스케줄러 루프를 붙여 보면 협력형 멀티태스킹이 어떻게 굴러가는지 손에 잡히고, 소켓 레벨로 내려가고 싶을 때 stream_select 로 이벤트 대기가 이어집니다. 외부 명령이 필요한 작업은 proc_open 으로 자식 프로세스 실행이 담당합니다.

직접 만든 동시성 코드에서 가장 흔한 정지 원인은 두 가지입니다. 첫째, Fiber 안에서 file_get_contents나 PDO 질의처럼 블로킹 호출을 하면 스케줄러 전체가 멈춥니다. Fiber는 어디까지나 협력형이라 스스로 suspend 하지 않는 코드는 양보하지 않습니다. 둘째, proc_open으로 띄운 자식의 표준 출력 파이프를 읽지 않으면 OS 파이프 버퍼가 가득 찬 순간 자식이 쓰기에서 멈추고, 부모는 종료를 기다리며 서로 교착합니다. 자식을 기다리기 전에 파이프를 먼저 비우거나 논블로킹으로 설정해 두십시오.

01curl_multi 로 병렬 요청

가벼운 병렬 HTTP 호출이 필요할 때 가장 먼저 떠올릴 수 있는 curl_multi 예제입니다.

PHP code

<?php
$urls = ['https://example.com', 'https://example.org'];
$multi = curl_multi_init();
$handles = [];

foreach ($urls as $url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 3,
    ]);
    curl_multi_add_handle($multi, $ch);
    $handles[] = $ch;
}

do {
    curl_multi_exec($multi, $running);
    curl_multi_select($multi);
} while ($running > 0);

echo count($handles);
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

curl 확장이 설치되어 있지 않으면 예제 자체가 동작하지 않습니다.

02Fiber 기본 사용

PHP 8.1 Fiber로 실행 흐름을 일시 중단하고 재개하는 가장 작은 예제입니다.

PHP code

<?php
$fiber = new Fiber(function (): void {
    echo "fiber start
";
    Fiber::suspend('paused');
    echo "fiber resume
";
});

echo $fiber->start() . PHP_EOL;
$fiber->resume();
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

03Generator 코루틴 흉내

Fiber가 없더라도 generator로 코루틴 스타일 흐름을 이해할 수 있는 예제입니다.

PHP code

<?php
function task(): Generator {
    echo "before
";
    yield 'step-1';
    echo "after
";
}

$gen = task();
echo $gen->current() . PHP_EOL;
$gen->next();
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

04stream_select 로 이벤트 대기

여러 스트림의 준비 상태를 동시에 기다리는 stream_select 예제입니다.

PHP code

<?php
$read = [STDIN];
$write = [];
$except = [];

if (stream_select($read, $write, $except, 0, 200000) > 0) {
    echo "stdin ready
";
} else {
    echo "no input
";
}
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

05논블로킹 스트림 읽기

스트림을 논블로킹 모드로 전환해 비동기 루프에 연결할 수 있음을 보여주는 예제입니다.

PHP code

<?php
$stream = fopen('php://temp', 'r+');
fwrite($stream, "hello");
rewind($stream);
stream_set_blocking($stream, false);

echo stream_get_contents($stream);
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

06proc_open 으로 자식 프로세스 실행

긴 작업을 별도 프로세스로 분리할 때 쓸 수 있는 proc_open 예제입니다.

PHP code

<?php
$descriptor = [
    1 => ['pipe', 'w'],
];

$process = proc_open('php -r "echo 123;"', $descriptor, $pipes);
echo stream_get_contents($pipes[1]);
proc_close($process);
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

07간단한 스케줄러 루프

외부 라이브러리 없이도 작업 큐를 순차 스케줄링하는 기본 구조를 이해하는 예제입니다.

PHP code

<?php
$tasks = [
    fn () => print("job-1
"),
    fn () => print("job-2
"),
];

while ($task = array_shift($tasks)) {
    $task();
}
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

08지수 백오프 폴링

외부 API 재시도 시 자주 쓰는 지수 백오프 개념을 코드로 풀어낸 예제입니다.

PHP code

<?php
$delay = 1;
for ($attempt = 1; $attempt <= 3; $attempt++) {
    echo "attempt {$attempt}
";
    $delay *= 2;
}

echo "next delay: {$delay}
";
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

09parallel 확장 사용 여부 체크

고급 동시성 확장 사용 전 런타임 준비 상태를 먼저 점검하는 예제입니다.

PHP code

<?php
if (!extension_loaded('parallel')) {
    echo "parallel extension required
";
    return;
}

echo "parallel ready
";
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

10curl_multi 배치 API 요청

같은 형태의 외부 API 요청을 배치로 실행할 때 쓰는 패턴 예제입니다.

PHP code

<?php
$ids = [101, 102, 103];
$multi = curl_multi_init();

foreach ($ids as $id) {
    $ch = curl_init("https://example.com/users/{$id}");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($multi, $ch);
}

echo count($ids);
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

11Fiber 취소 플래그

실행 중인 작업을 외부 플래그로 중단하는 간단한 협력 취소 예제입니다.

PHP code

<?php
$cancelled = false;

$fiber = new Fiber(function () use (&$cancelled): void {
    while (!$cancelled) {
        Fiber::suspend();
    }
});

$fiber->start();
$cancelled = true;
$fiber->resume();
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

12작업 큐 소비 루프

백그라운드 워커가 큐를 하나씩 소비하는 가장 단순한 구조 예제입니다.

PHP code

<?php
$queue = new SplQueue();
$queue->enqueue('email');
$queue->enqueue('thumbnail');

while (!$queue->isEmpty()) {
    echo 'run: ' . $queue->dequeue() . PHP_EOL;
}
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

13프로세스 상태 폴링

외부 프로세스가 끝났는지 주기적으로 확인하는 상태 폴링 예제입니다.

PHP code

<?php
$process = proc_open('php -r "usleep(100000); echo 1;"', [1 => ['pipe', 'w']], $pipes);

do {
    $status = proc_get_status($process);
} while ($status['running']);

echo stream_get_contents($pipes[1]);
proc_close($process);
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

14비동기 라이브러리 진입점 형태

ReactPHP나 Amp 같은 비동기 라이브러리를 붙일 때 보게 되는 최소 진입점 형태입니다.

PHP code

<?php
require __DIR__ . '/vendor/autoload.php';

echo "bootstrap async runtime";
알아두면 좋은 점

PHP의 동시성 예제는 어떤 확장이나 런타임이 필요한지 먼저 확인한 뒤 따라 하면 시행착오를 줄일 수 있습니다.

자주 하는 실수

비동기처럼 보여도 내부에서 블로킹 호출을 하면 실제로는 순차 실행과 다르지 않습니다.

정리하며

  • 요청 지연이 병목이면 먼저 curl_multi로 대기 시간만 겹쳐도 큰 효과를 얻습니다.
  • Fiber는 스케줄러를 제공하지 않으므로 루프와 재개 조건을 직접 설계해야 합니다.
  • 협력형 코드 안에서는 블로킹 함수 한 번이 전체를 멈춘다는 점을 전제로 짭니다.
  • proc_open 사용 시 자식 파이프를 먼저 읽어 버퍼 포화로 인한 교착을 막습니다.

더 깊이 들어가고 싶다면 PHP 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.