Можете ли вы выяснить этот вопрос синхронизации PHP?
Может ли кто-нибудь сказать мне, почему, когда я запустил script с приведенным ниже содержимым, а затем остановил его после 5 секунд, что мне нужно разделить прошедшее время на 2, чтобы получить правильный script время выполнения?
ignore_user_abort(true); set_time_limit(0);
$begin_time = microtime(true);
$elapsed_time = 0;
while(!connection_aborted()) {
echo ' ';
flush();
usleep(1000000);
}
$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds
$timer_seconds = $elapsed_time / 2; //5 seconds
/*I am writing to a DB - but you can use this to test */
$fp = fopen('times.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fclose($fp);
Не стесняйтесь попробовать код, так как он озадачил меня тем, почему $elapsed_time
нужно разделить на два. Может, я что-то не понял?
Спасибо всем за помощь
Update
Я обновил код, чтобы кто-нибудь мог попробовать это, и он будет записывать в текстовый файл для просмотра вывода.
Ответы
Ответ 1
Эксперимент:
Значительные изменения от исходного кода:
1) Использование implicit_flush, и все буферы очищаются, прежде чем что-либо делать.
2) Вместо того, чтобы выводить только пробел, код выводит номер итерации и 1023 байта других данных, чтобы сообщить браузеру, что у нас есть хороший объем вывода для отображения. Обычный известный трюк.
3) Наряду с сохранением времени в выходном текстовом файле, он также сохраняет общие итерации, которые выполнял код.
Используемый код:
<?php
// Tricks to allow instant output
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
ob_end_flush();
ob_implicit_flush(1);
//Your Code starts here
ignore_user_abort(true);
set_time_limit(0);
$begin_time = microtime(true);
$elapsed_time = 0;
while(!connection_aborted())
{
//this I changed, so that a looooong string is outputted
echo $i++.str_repeat(' ',1020).'<br/>';
flush();
usleep(1000000);
}
$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds
//Writes to file the number of ITERATIONS too along with time
$fp = fopen('4765107.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fwrite($fp, "\nIterations: ".$i);
fclose($fp);
?>
Live Demo:
Что я получил:
1) Когда код запускается для 10 итераций и нажата кнопка STOP в браузере, выходной файл показывает 13 итераций с ~ 13.01 секунд.
2) Когда код запускается для 20 итераций и нажата кнопка STOP в браузере, выходной файл показывает 23 итерации с ~ 23.01 секунд.
Выводы и вывод:
1) script на самом деле НЕ останавливается, когда нажата кнопка STOP, но через 2-4 секунды щелкнув по ней. Итак, есть больше итераций, которые отображаются в браузере.
2) Число итераций равно SAME как количество секунд, которое требуется для выполнения, как показано в выходном файле.
Следовательно, нет ошибок и, по-видимому, нет ошибок, это просто время ожидания между нажатием кнопки STOP и script на самом деле останавливается.
Примечания:
1) Сервер: Linux VPS.
2) Клиенты протестированы: Firefox и Chrome.
3) По мере того, как script заканчивается через 2-4 секунды после нажатия кнопки STOP, для текущего файла для обновления текущего файла требуется около 3-4 секунд.
Ответ 2
Резюме: (этот пост стал эпическим, поскольку я тестировал различные возможности)
PHP принимает -типически итерации с двумя циклами для обнаружения отключения или доставки вывода. Эта задержка может исходить от программного обеспечения веб-сервера, хост-компьютера, клиентского компьютера и браузера клиента, но затем оно должно меняться в зависимости от сна на итерацию. Скорее, задержка возникает из внутреннего выполнения или выходного процесса PHP (возможно, из небольшого внутреннего буфера или процесса обработки прерываний).
Epic Post:
Подсчет времени выполнения от [Обновить] или URL-отправления не является точно точной начальной точкой - сначала может потребоваться любое количество шагов и может добавить к задержке:
- Требуется поиск DNS (с накладными расходами TCP)
- TCP-соединение, установленное с сервером
- Веб-сервер создает поток или дочерний элемент
- Веб-сервер решает, как справиться с запросом
- PHP, возможно, потребуется запустить
- PHP может потребоваться преобразовать ваш источник в opcode
Поэтому вместо измерения времени [Refresh] → [Stop] и сравнения его с числом, записанным PHP, я измерил отображаемый вывод на записанный результат, что уменьшает измерение задержки в основном только внутри PHP (хотя Server/Browser будет эффект). Измененный script не может убежать (он заканчивается после фиксированного количества итераций), очищает буферизацию по умолчанию php.ini
и сообщает счетчик итераций на экране и в файле синхронизации. Я провел script с различными периодами $sleep
, чтобы увидеть эффекты. Окончательный script:
<?php
date_default_timezone_set('America/Los_Angeles'); //Used by ob apparently
ignore_user_abort(true); //Don't terminate script because user leaves
set_time_limit(0); //Allow runaway script !danger !danger
while (@ob_end_flush()) {}; //By default set on/4K in php.ini
$start=microtime(true);
$n=1000;
$i=0;
$sleep=100000;// 0.1s
while(!connection_aborted() && $i<$n) {
echo "\n[".++$i."]";flush();
usleep($sleep);
}
$end=microtime(true);
file_put_contents("timing.txt",
"#\$sleep=".($sleep/1000000).
"s\n/ s=$start / e=$end ($i) / d=".($end-$start)."\n",
FILE_APPEND);
?>
Результаты: (несколько запусков конкатенированы, запускаются в Firefox)
# On-screen $i / start utime / end utime (current $i) / delta utime
#$sleep=1s
2 / s=1296251342.5729 / e=1296251346.5721 (4) / d=3.999242067337
3 / s=1296251352.9094 / e=1296251357.91 (5) / d=5.000559091568
#$sleep=0.1s
11 / s=1296251157.982 / e=1296251159.2896 (13) / d=1.3075668811798
8 / s=1296251167.5659 / e=1296251168.5709 (10) / d=1.0050280094147
16 / s=1296251190.0493 / e=1296251191.8599 (18) / d=1.810576915741
4 / s=1296251202.7471 / e=1296251203.3505 (6) / d=0.60339689254761
16 / s=1296251724.5782 / e=1296251726.3882 (18) / d=1.8099851608276
#$sleep=0.01s
42 / s=1296251233.0498 / e=1296251233.5217 (44) / d=0.47195816040039
62 / s=1296251260.4463 / e=1296251261.1336 (64) / d=0.68735003471375
150 / s=1296251279.2656 / e=1296251280.901 (152) / d=1.6353850364685
379 / s=1296252444.7587 / e=1296252449.0108 (394) / d=4.2521529197693
#$sleep=0.001s
337 / s=1296251293.4823 / e=1296251294.1515 (341) / d=0.66925406455994
207 / s=1296251313.7312 / e=1296251314.1445 (211) / d=0.41328597068787
792 / s=1296251324.5233 / e=1296251326.0915 (795) / d=1.5682451725006
(Opera не отображает числа во время, но отображает окончательные номера, которые примерно совпадают)
(Chrome не отображает ничего во время/после теста)
(Safari не отображает ничего во время/после теста)
(IE не отображает ничего во время/после теста)
Первое число в каждой строке указывает номер, отображаемый на экране, когда нажата [остановка] (записана вручную).
Несколько точек:
- Ваша точка останова квантуется до ближайшего периода
$sleep
(1/10 с в приведенном выше script), поскольку script проверяет только в начале каждого, пока итерация, существует некоторая вариация, потому что метод usleep
не является идеальной задержкой.
- Используемый вами браузер и сервер имеют значение. flush заметки на странице руководства "не могут переопределить схему буферизации вашего веб-сервера и не влияют на клиентскую сторону буферизация в браузере." Далее идет подробная информация о проблемах с сервером и клиентом. [Сервер: WinXPsp3/Apache 2.2.17/PHP 5.3.3 Клиент: WinXPsp3/FireFox 3.6.13]
Анализ:
Во всех случаях, кроме задержки 0.001s, мы наблюдаем 2 задержки итерации между [остановкой] и PHP, улавливающими ее (или отчеты Firefox или Apache). При задержке 0.001s он немного меняется, а средний - ~ 4 итерации или 0.004 - это, вероятно, приближается к порогу скорости обнаружения.
Когда время задержки составляет 0,1 с или выше, мы видим, что время выполнения близко соответствует $sleep
* {записанным итерациям}.
Когда время задержки ниже 0,1 с, мы наблюдаем задержки выполнения, превышающие время ожидания. Вероятно, это связано с затратами на проверку соединения с клиентом, увеличением $i
, выводом текста и сбросом буфера на итерацию. Расхождение между временем выполнения и $i*$sleep
довольно линейно, предполагая, что для выполнения этих задач требуется ~ 0.001s/итерация (с 0,01 спящим его 0,0008, а сон 0,001s - 0,0010 - вероятно, результат увеличения MALLOC
/выход).
Ответ 3
Вы полагаетесь на connection_aborted()
, чтобы быть true
, когда вы нажмете кнопку "Стоп" в своем браузере, но не указали никаких доказательств того, что вы подтвердили, что это так. На самом деле это не так.
Вы забыли, как проверка соединения прекращена в сети. Приложение (php в этом случае) не знает, что произошло до тех пор, пока оно не попытается записать в трубу.
Первый комментарий к в документации для connection_abort()
гласит:" Чтобы обнаружить разъединение внутри script, нам нужно сбросить (только тогда, когда сервер пытается отправить содержимое буфера, что он увидит, что соединение сломано).
Поэтому я не считаю, что вы могли бы надежно использовать connection_abort()
таким образом.
Будьте уверены, microtime()
работает правильно.
Ответ 4
connection_aborted()
может обнаруживать отключение при отправке буфера. Но flush()
необязательно отправляет буфер. Таким образом, цикл продолжает итерацию до тех пор, пока буфер не будет заполнен и действительно не разрядится.
Подробнее см. на страницах руководства названных функций.
Ответ 5
Используя ваш script из коробки, не работает должным образом на Ubuntu, используя Chrome для доступа к странице.
Цикл просто продолжается, пришлось перезапустить Apache.
На другом конце, добавив ob_end_flush() вверху, разрешает эту проблему, плюс таймер на самом деле правильный.
ob_end_flush();
ignore_user_abort(true);
set_time_limit(0);
$begin_time = microtime(true);
$elapsed_time = 0;
while(!connection_aborted()) {
echo ' ';
flush();
usleep(1000000);
error_log("looping");
}
$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time;
error_log(var_export($timer_seconds, true));
$timer_seconds = $elapsed_time / 2;
error_log(var_export($timer_seconds, true));
Если вы запустите это и посмотрите на журнал ошибок, вы увидите, что $elpased_time верен в первом прогоне, не нужно также делить его. Как почему ваш код ведет себя так, я не знаю, поскольку он даже не работает на моей машине.
Ответ 6
Это не "проблема", а "дизайн"
Это функция того, как работает http.
При отправке веб-страницы клиенту (браузеру) сервер "MUST" отправляет заголовок длины содержимого. Теперь он не может знать длину содержимого, пока он не получил все из script.
Таким образом, серверы буферизуют вывод script до завершения script.
Здесь происходят капризы. В зависимости от сервера и даже разных версий одного и того же сервера он может или не может проверить, отключен ли клиент на регулярной основе, и если это произойдет, эти проверки могут отличаться временные интервалы. который может даже измениться в зависимости от того, насколько занят сервер.
PHP не имеет контроля над соединением с клиентом, он может запрашивать только сервер, если соединение все еще существует. Сервер может или не может сказать правду. Таким образом, script продолжает работать (в то время, когда сервер может не знать).
Итак, почему Микуши работал после добавления ob_end_flush() в START из script?
Хорошо, потому что он включил еще одну функцию http, называемую chunk transfer. Это позволяет отправлять данные в куски, каждый со специальной версией заголовка длины содержимого (на самом деле он не говорит, что он просто отправляет следующую длину блока)
Попробуйте Mikushi script с Wireshark, и вы увидите кодировку, здесь показан пример
HTTP/1.1 200 OK
Date: Tue, 01 Feb 2011 11:52:35 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
7 <== this is the content-length
<pre>0
2 <== this is the content-length
1
2 ditto ...
2
2
3
2
4
2
5
Итак, это означает, что это невозможно (да, Томалак, который вы мне дали:)), чтобы знать, когда сервер прекратит соединение, и поэтому вернет true для connection_aborted(), пока вы не проверите фактический сервер. Потому что каждый из них отличается. Даже веб-браузер может делать некоторые вещи, которые задерживают фактическое закрытие, что может еще больше запутать проблему.
DC