Идентификатор потока в PHP: методы и примеры

Раздел: Программирование на PHP -> Потоки PHP

Что такое идентификатор потока и зачем он нужен

Идентификатор потока (stream id) это уникальный числовой номер, присваиваемый каждому открытому потоку в PHP. Потоками могут быть файлы, сокеты, HTTP соединения, каналы proc_open и другие. Знание идентификатора полезно для отслеживания потоков в логах, сравнения двух переменных на принадлежность одному потоку, организации мультиплексирования при работе с сокетами и предотвращения утечек ресурсов.

Основное решение функция stream_get_id()

Как получить идентификатор любого потока в PHP 8 и выше?

Начиная с PHP 8.0.0 в языке появилась встроенная функция stream_get_id, которая принимает потоковый ресурс (объект) и возвращает его числовой идентификатор (int). Это наиболее прямой и эффективный способ.


<?php
// Открываем файловый поток
$file = fopen('/tmp/test.txt', 'w');
// Получаем ID
$id = stream_get_id($file);
echo "ID файлового потока: $id\n";
fclose($file);

// Открываем TCP сокет
$sock = stream_socket_client('tcp://example.com:80');
$sockId = stream_get_id($sock);
echo "ID сокетного потока: $sockId\n";
fclose($sock);
?>
ID файлового потока: 4
ID сокетного потока: 5

Цель использования быстрый доступ к уникальному ключу для идентификации потока. Например, для логирования или в циклах с stream_select.

Проблема: функция доступна только начиная с PHP 8.0. Если код выполняется на более старой версии, возникает фатальная ошибка.

Решение: перед вызовом проверить существование функции через function_exists.


if (function_exists('stream_get_id')) {
    $id = stream_get_id($stream);
} else {
    // альтернативный метод
}

Вариант 1 приведение потока к целому числу (для PHP 7 и ниже)

Как получить идентификатор потока, если stream_get_id недоступна?

В PHP 7 и более ранних версиях поток представлял собой ресурс. Приведение ресурса к целому числу с помощью intval() или (int) возвращало его внутренний идентификатор. В PHP 8 ресурсы заменены на объекты, поэтому этот способ перестал работать.


<?php
// старый способ (PHP 7)
$file = fopen('/tmp/test.txt', 'r');
$id = (int) $file;  // или intval($file)
echo "ID потока (старый): $id\n";
fclose($file);
?>
ID потока (старый): 3

Проблема: после PHP 8 такой код выдаст ошибку, так как объект нельзя привести к int. Кроме того, в PHP 7 идентификатор мог быть переиспользован после закрытия потока.

Решение: использовать только для устаревших проектов и обязательно проверять версию PHP. Рекомендуется по возможности перейти на stream_get_id.

Цель использования поддержка обратной совместимости в коде, который раньше полагался на этот трюк.

Вариант 2 spl_object_id() для объектов потоков

Можно ли использовать spl_object_id вместо stream_get_id?

В PHP 8 потоки являются объектами (экземплярами внутреннего класса, например, stream или Socket). Функция spl_object_id возвращает уникальный идентификатор любого объекта в течение его жизни. Однако этот идентификатор не обязательно совпадает с внутренним номером потока, используемым системой. Более того, он может быть повторно использован для другого объекта после удаления исходного. Поэтому не рекомендуется полагаться на него для идентификации потоков.


<?php
$f = fopen('/tmp/test.txt', 'r');
$oid = spl_object_id($f);
echo "spl_object_id: $oid\n";
$id = stream_get_id($f);
echo "stream_get_id: $id\n";
fclose($f);
?>
spl_object_id: 1
stream_get_id: 3

Как видно, значения различаются. Использование spl_object_id может быть оправдано только в контексте отслеживания самого объекта, а не системного потока.

Проблема: несоответствие системному идентификатору, потенциальная путаница. Нельзя сравнивать с идентификаторами, полученными извне (например, из логов сервера).

Цель редкие случаи, когда нужно просто отличать один объект потока от другого без привязки к системному номеру.

Вариант 3 функция get_resource_id() (устаревшая)

Какой был способ до появления stream_get_id в PHP 7?

В PHP 7 существовала функция get_resource_id(), которая возвращала идентификатор ресурса. Она работала только с ресурсами, а не с объектами потоков. После перехода на объекты в PHP 8 она также устарела и в конечном итоге была удалена. Если в проекте используется PHP 7, можно применить её, но лучше переписать код.


<?php
// PHP 7
$file = fopen('/tmp/test.txt', 'r');
$id = get_resource_id($file);
echo "get_resource_id: $id\n";
fclose($file);
?>

Проблема: функция не поддерживается в PHP 8, вызовет ошибку. Кроме того, в PHP 7.2 она уже была помечена как устаревшая.

Решение: заменить на stream_get_id при обновлении PHP.

Цель временное решение для уже написанного кода на PHP 7.x.

Вариант 4 использование stream_get_meta_data и других обходных путей

Как идентифицировать поток без встроенного ID?

Иногда можно обойтись без числового идентификатора, анализируя метаданные потока. Функция stream_get_meta_data возвращает информацию о потоке, включая тип, режим, uri (если доступен). Для уникальной идентификации можно комбинировать эти данные, но это не даёт единого числа.


<?php
$f = fopen('/tmp/test.txt', 'r');
$meta = stream_get_meta_data($f);
// Выводим uri и тип
echo "URI: " . $meta['uri'] . "\n";
echo "Type: " . $meta['stream_type'] . "\n";
fclose($f);
?>

Проблема: не является идентификатором в строгом смысле; два потока могут иметь одинаковые метаданные (например, один и тот же файл, открытый дважды). Не подходит для точного сравнения.

Решение: только в ситуациях, когда числовой ID не требуется, а нужно узнать, откуда поток.

Цель отладка, логирование, диагностика.

Расширенные примеры использования идентификатора потока

Пример 1 несколько открытых потоков и их ID

Демонстрация того, как разные потоки получают уникальные идентификаторы.

Пример

<?php
$f1 = fopen('/tmp/file1.txt', 'w');
$f2 = fopen('/tmp/file2.txt', 'w');
$s1 = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);

$ids = [
    'file1' => stream_get_id($f1),
    'file2' => stream_get_id($f2),
    'server' => stream_get_id($s1),
];

print_r($ids);

fclose($f1); fclose($f2); fclose($s1);
?>
Array
(
    [file1] => 4
    [file2] => 5
    [server] => 6
)

Пример 2 использование stream_select и идентификация потоков по ID

В цикле обработки событий можно отслеживать активные потоки по их идентификаторам.

Пример

<?php
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
$clients = [];

// добавляем сервер в массив для чтения
$read = [$server];

while (true) {
    $write = $except = null;
    if (stream_select($read, $write, $except, 0) > 0) {
        foreach ($read as $stream) {
            $id = stream_get_id($stream);
            echo "Готов к чтению поток ID: $id\n";
            // обработка...
        }
    }
    // лимит на демонстрацию
    break;
}
fclose($server);
?>
Готов к чтению поток ID: 5

Пример 3 сравнение потоков через ID

Проверка, ссылаются ли две переменные на один и тот же поток.

Пример

<?php
$f = fopen('/tmp/test.txt', 'r');
$g = $f; // присваиваем ссылку
$h = fopen('/tmp/test.txt', 'r'); // открываем отдельно

$idF = stream_get_id($f);
$idG = stream_get_id($g);
$idH = stream_get_id($h);

echo "f: $idF, g: $idG, h: $idH\n";
echo "f === g: " . ($idF === $idG ? 'true' : 'false') . "\n";
echo "f === h: " . ($idF === $idH ? 'true' : 'false') . "\n";

fclose($f); fclose($h);
?>
f: 4, g: 4, h: 5
f === g: true
f === h: false

Пример 4 пользовательский stream wrapper и его ID

Создание простого обёртки потока и получение идентификатора объекта.

Пример

<?php
class MyStream {
    private $data;
    public function stream_open($path, $mode, $options, &$opened_path) {
        $this->data = 'test';
        return true;
    }
    public function stream_read($count) { return substr($this->data, 0, $count); }
    public function stream_write($data) { $this->data .= $data; return strlen($data); }
    public function stream_eof() { return empty($this->data); }
}

stream_wrapper_register('my', MyStream::class);
$s = fopen('my://test', 'r+');
echo "ID пользовательского потока: " . stream_get_id($s) . "\n";
fclose($s);
?>
ID пользовательского потока: 3

Пример 5 получение ID сокета сервера и его клиентов

Демонстрация идентификации серверного и клиентских сокетов в простом TCP сервере.

Пример

<?php
$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
echo "Сервер ID: " . stream_get_id($server) . "\n";

// имитируем подключение клиента
$client = stream_socket_client(stream_socket_get_name($server, false));
$accepted = stream_socket_accept($server, 0);
echo "Принятый клиент ID: " . stream_get_id($accepted) . "\n";
echo "Клиент (исходный) ID: " . stream_get_id($client) . "\n";

fclose($client); fclose($accepted); fclose($server);
?>
Сервер ID: 4
Принятый клиент ID: 5
Клиент (исходный) ID: 6

Идентификатор потока PHP - comments

En
Stream php id (php)