Php usleep() function

Потоковые библиотеки

Потокам выполнения нужна помощь ядра ОС. В операционных системах потоки выполнения появились в середине 1990-х, так что методики работы с ними отшлифованы.

Но существуют проблемы кроссплатформенности. Особенно много различий между Windows- и Unix-системами. В этих экосистемах приняты разные модели потокового выполнения и используются разные потоковые библиотеки.

В Linux для создания потока или процесса ядро осуществляет системный вызов clone(). Но он невероятно сложен, поэтому для облегчения повседневного потокового программирования системные вызовы используют код на Си. Libc до сих пор не управляет потоковыми операциями (подобную инициативу демонстрирует стандартная библиотека из С11), этим занимаются внешние библиотеки. Сегодня в Unix-системах обычно применяется pthread (есть и другие библиотеки). Pthread — сокращение от Posix threads. Эта POSIX-нормализация использования потоков и их поведения берёт своё начало в 1995-м. Если вам нужны потоки выполнения, подключите библиотеку libpthread: передайте в GCC —lpthread. Она написана на С, её код открыт, есть собственный механизм контроля и управления версиями.

Итак, в Unix-системах чаще всего используется библиотека pthread. Она обеспечивает согласованность (concurrency), а параллелизм зависит от конкретной ОС и компьютера.

Согласованность — это когда несколько потоков беспорядочно выполняются на одном процессоре. Параллелизм — это когда несколько потоков одновременно выполняются на разных процессорах.

Согласованность:

Параллелизм:

Схема памяти потоков выполнения

У потоков выполнения есть свой стек. Поэтому при обращении к переменным, объявленным в функциях, они получают собственную копию этих данных.

Куча процесса совместно используется потоками выполнения, как и глобальные переменные, и файловые дескрипторы. Это и преимущество, и недостаток. Если мы только считываем из глобальной памяти, то нужно это делать вовремя. Например, после потока Х и до потока Y. Если мы пишем в глобальную память, то стоит удостовериться, что туда же и в то же время не попытаются записать несколько потоков. Иначе эта область памяти окажется в непредсказуемом состоянии — так называемом состоянии гонки (race condition). Это главная проблема в потоковом программировании.

На случай одновременного доступа вам нужно внедрить в код некоторые механизмы вроде повторного входа (reentrancy) или подпрограмм синхронизации (synchronization routine). Повторный вход нарушает согласованность (concurrency). А синхронизация позволяет управлять согласованностью предсказуемым образом.

Процессы не используют память совместно, они идеально изолированы на уровне ОС. А потоки выполнения внутри одного процесса совместно используют большой объём памяти.

Поэтому им нужны инструменты синхронизации доступа к общей памяти, например семафоры и мьютексы (mutexes). Работа этих инструментов основана на принципе «блокировки»: если ресурс заблокирован, а поток пытается получить к нему доступ, то по умолчанию поток будет ожидать разблокировки ресурса. Поэтому потоки выполнения сами по себе не сделают вашу программу быстрее. Без эффективного распределения задач по потокам и управления блокировками общей памяти ваша программа станет работать ещё медленнее, чем при использовании одного процесса без потоков выполнения. Просто потоки будут постоянно ожидать друг друга (и я ещё не говорю о взаимоблокировках, голодании и т. д.).

Если у вас нет опыта в потоковом программировании, то оно окажется для вас непростым занятием. Чтобы наработать опыт работы с потоками выполнения, понадобится много часов практики и решения WTF-моментов. Стоит забыть о какой-то мелочи — и вся программа пойдёт в разнос. Труднее отлаживать программу с потоками, чем без них, если мы говорим о реальных проектах с сотнями или тысячами потоков в одном процессе. Вы сойдёте с ума и просто утонете во всём этом.

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

Такая схема совместного использования памяти потоками не всегда удобна. Поэтому появилось локальное хранилище потока (Thread Local Storage, TLS). TLS можно описать как «глобалы, принадлежащие какому-то одному потоку и не используемые другими». Это области памяти, отражающие глобальное состояние, приватные для конкретного потока выполнения (как в случае использования одних лишь процессов). При создании потока выделяется часть кучи процесса — хранилище. У потоковой библиотеки запрашивается ключ, который ассоциируется с этим хранилищем. Он должен использоваться потоком выполнения при каждом обращении к своему хранилищу. Для уничтожения выделенных ресурсов в конце жизни потока требуется деструктор.

Приложение «потокобезопасно» (thread safe), если каждое обращение к глобальным ресурсам находится под полным контролем и полностью предсказуемо. В противном случае вам перейдёт дорогу диспетчер (scheduler): будут неожиданно выполняться какие-то задачи, и производительность упадёт.

Caution:

The following list of the point should be kept in mind on writing PHP sleep programs. These points are used to be aware of any failure or warning notice, that may cause while invoking PHP sleep functions.

  • If we send negative values to PHP sleep functions, then it will display warning error notice to the browser, as follows.
    Warning: sleep(): Number of seconds must be greater than or equal to 0
    
  • Also sending arguments with another data type except for integer value will cause PHP warning error, like,
    Warning: sleep() expects parameter 1 to be long
    
  • While using time_sleep_until() function, if we are not specifying upcoming temporal data, rather passing old timestamp, then, this function will cause warning notice.

Уровень TSRM

ZTS использует так называемый уровень TSRM — Thread Safe Resource Manager. Это просто кусок кода на С, ничего более!

Именно благодаря уровню TSRM возможна работа ZTS. По большей части он находится в папке /TSRM исходного кода PHP.

TSRM — не идеальный уровень. В целом он спроектирован хорошо и появился ещё во времена начала PHP 5 (около 2004-го). TSRM может работать с несколькими низкоуровневыми потоковыми библиотеками: Gnu Portable Thread, Posix Threads, State Threads, Win32 Threads и BeThreads. Желаемый уровень можно выбрать при конфигурировании (./configure —with-tsrm-xxxxx).

При анализе TSRM мы будем обсуждать только реализацию на основе pthreads.

Необходимость макросов

Запомните: когда PHP работает в потоках выполнения, необходимо защищать доступ ко всем глобальным состояниям, относящимся к запросам. Если потоков нет, то эта защита не нужна. Ведь каждый процесс получает собственную память, которой не пользуется никто другой.

Так что способ доступа к глобалам, относящимся к запросам, зависит от окружения (используется многозадачный движок). Поэтому надо сделать так, чтобы обращение к глобалам, связанным с запросами, выполнялось одинаково вне зависимости от окружения.

Для этого используются макросы.

Макрос будет обрабатываться разными способами, в соответствии с работой многозадачного движка PHP (процессы или потоки). Вы можете на это влиять, перекомпилируя своё расширение. Поэтому расширения PHP несовместимы при переключении между режимами ZTS и неZTS. Несовместимы на уровне двоичного кода (binary incompatible)!

ZTS несовместим на уровне двоичного кода с неZTS. При переключении с одного режима на другой нужно перекомпилировать исключения.

При работе в процессе макрос обычно обрабатывается так:

При работе в потоке:

В ZTS-режиме сложнее.

При работе в процессе — режим неZTS (Non Zend Thread Safe) — используется истинный глобал, wow_globals. Эта переменная представляет собой структуру, содержащую глобальные переменные, и с помощью макроса мы обращаемся к каждой из них. ведёт на . Естественно, вам нужно объявить эту переменную, чтобы она была обнулена при запуске. Это также делается с помощью макроса (в ZTS-режиме выполняется иначе):

Тогда макрос обрабатывается так:

И всё. При работе в процессе — ничего сложного.

Но при работе в потоке — с использованием ZTS — у нас больше нет истинных глобалов С. Но объявления глобалов выглядят так же:

В ZTS и неZTS глобалы объявляются одинаково.

Но доступ к ним происходит по-разному. В ZTS вызывается функция . Это обращение к хранилищу TLS, которое возвратит область памяти, выделенную для текущего конкретного потока выполнения. Учитывая, что в первую очередь мы выполняем приведение к типу void, с этим кодом всё не так просто.

Несколько слов о потоках выполнения

Освежим в памяти, что такое потоки выполнения. Не будем углубляться в подробности, их вы найдёте в интернете и книгах.

Поток выполнения — «малая» единица обработки (light unit of work treatment), находящаяся внутри процесса. Процесс может создавать многочисленные потоки выполнения. Поток должен быть частью только одного процесса. Процесс — «большая» единица обработки в рамках операционной системы. На многоядерных (многопроцессорных) компьютерах несколько ядер (процессоров) работают параллельно и обрабатывают часть нагрузки исполняемых задач. Если процессы А и Б готовы к постановке в очередь и два ядра (процессора) готовы к работе, то А и Б должны быть одновременно отправлены в обработку. Тогда компьютер эффективно обрабатывает несколько задач в единицу времени (временной интервал, timeframe). Мы называем это «параллелизм».

Процесс:

Поток выполнения:

Всё вместе:

Раньше A и Б были процессами: полностью независимыми обработчиками. Но потоки выполнения — это не процессы. Потоки — это единицы, живущие внутри процессов. То есть процесс может распределить работу по нескольким более мелким задачам, выполняемым одновременно. К примеру, процессы А и Б могут породить потоки А1, А2, Б1 и Б2. Если компьютер оснащён несколькими процессорами, например восемью, то все четыре потока могут выполняться в одном временном интервале (timeframe).

Потоки выполнения — это способ разделения работы процесса на несколько мелких подзадач, решаемых параллельно (в одном временном интервале). Причём потоки выполняются примерно так же, как и процессы: диспетчер программного потока ядра (Kernel thread scheduler) управляет потоками с помощью состояний.

Потоки выполнения легче процессов, им для работы нужен лишь стек и несколько регистров. А процессам требуется много всего: новый кадр виртуальной машины (VM frame) от ядра, куча, разная сигнальная информация, информация о файловых дескрипторах, блокировках и т. д.

Память процесса управляется на аппаратном уровне ядром и MMU, а память потока выполнения — на программном уровне программистом и потоковыми библиотеками (threading library).

Так что запомните: потоки выполнения легче процессов и проще управляются. При грамотном использовании они ещё и работают быстрее процессов, поскольку ядро ОС почти не вмешивается в управление потоками и их диспетчеризацию.

Related PHP Functions Pausing Execution

There are three other functions deals with the operations of pausing PHP program execution. These differ from sleep() function with respect to the unit of time with which the parameter of these functions is passed.

These functions are listed below.

  1. usleep() – This function accepts microseconds instead of seconds received for sleep() function.
  2. time_nanosleep() – Unlike sleep() and usleep(), this function receives time parameter with the unit of seconds and nanoseconds.
  3. time_sleep_until() – this PHP function will get upcoming timestamp value with float data type for representing the time for sleep. Since the timestamp is a float, we can send this value with microseconds precision.

Как PHP обрабатывает запросы

Всё дело в том, как PHP будет обрабатывать HTTP-запросы. Веб-серверу необходимо обеспечить какую-то согласованность (или параллелизм) для обслуживания нескольких клиентов одновременно. Ведь, отвечая одному клиенту, нельзя поставить всех остальных на паузу.

Поэтому для ответов клиентам серверы обычно используют несколько процессов или несколько потоков выполнения.

Исторически сложилось, что под Unix работают процессы. Просто это основа Unix, с его рождением появились и процессы, способные создавать новые процессы (), уничтожать их () и синхронизировать (, ). В такой среде многочисленные PHP обслуживают многочисленные клиентские запросы. Но каждый работает в своём собственном процессе.

В такой ситуации PHP ничем не может помочь: процессы полностью изолированы. Процесс А, обрабатывающий запрос А о данных клиента А, не сможет взаимодействовать (читать или писать) с процессом Б, обрабатывающим запрос Б клиента Б. Нам это и нужно.

В 98 % случаев используются две архитектуры: php-fpm и Apache с mpm_prefork.

Под Windows всё сложнее, как и в Unix-серверах с потоками выполнения.

Windows — действительно замечательная ОС. У неё лишь один недостаток — закрытый исходный код. Но в сети или в книгах можно найти информацию о внутреннем устройстве многих технических ресурсов. Инженеры Microsoft много рассказывают о том, как работает Windows.

У Windows другой подход к согласованности и параллелизму. Эта ОС очень активно использует потоки выполнения. По сути, создание процесса в Windows — такая тяжёлая задача, что обычно её избегают. Вместо этого всегда и везде применяют потоки выполнения. Потоки в Windows на порядок мощнее, чем в Linux. Да, именно так.

Когда PHP работает под Windows, веб-сервер (любой) будет обрабатывать клиентские запросы в потоках, а не процессах. То есть в таком окружении PHP выполняется в потоке. И поэтому ему стоит особенно тщательно подходить к спецификациям потоков: он должен быть потокобезопасным (thread safe).

PHP должен быть потокобезопасным, т. е. управлять согласованностью, которую он не создавал, но в которой и с которой функционирует. То есть защитить свой доступ к своим собственным глобальным переменным. А их у PHP много.

За эту защиту отвечает уровень Zend Thread Safety (ZTS, потокобезопасность Zend).

Обратите внимание, что всё то же самое верно и под Unix, если вы решите использовать потоки выполнения для распараллеливания обработки клиентских запросов. Но для Unix-систем это очень необычная ситуация, поскольку для таких задач здесь традиционно используются классические процессы

Хотя никто не мешает выбрать потоки, это способно повысить производительность. Потоки легче процессов, так что система может выполнять гораздо больше потоков. Кроме того, если вашему PHP-расширению нужна потокобезопасность (вроде ext/pthread), то вам потребуется и потокобезопасный PHP.

Example: PHP sleep()

Let us have a PHP example program with sleep() function pausing execution with specified amount of time.

<?php
$sleep_time = rand(5,10);
echo "<b>Putting into sleep at</b>: " . date("H:i:s") . "<br/>";
sleep($sleep_time);
echo "<b>waked up after $sleep_time seconds. Current Time is: </b>" . date("H:i:s") . "<br/>";
?>

In the above program, we have used PHP random number generator, rand() function, for randomly selecting a number of seconds, the program to be put into sleep. For that, the range is specified from 5 to 10 seconds with rand().

Before invoking sleep with a randomly selected period of time, we have printed the time and repeat the same, after sleep is done. The program will display time in (Hour: Minute: Seconds) format, before and after sleep() function, and also, it will display how many seconds the program is paused using sleep(). And the output will be as shown below.

Putting into sleep at: 16:09:02
waked up after 5 seconds. Current Time is: 16:09:07
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector