Pull to refresh

Comments 95

Если вы воспользуетесь phpDaemon то у вас тоже будет «возможность иметь «состояние» приложения и многопоточную обработку запросов» на PHP.
ReactPHP лучше. Было несколько любопытных проектов запускать Symfony2 и Silex из под ReactPHP, получая при этом довольно неплохой прирост производительности за счет того что контейнер зависимостей всегда весит в памяти и не переинициализируется, так же как и пул соединений с базой и некоторые другие штуки. Но к сожалению я не знаю ни огдного проекта кто живет в продакшене на подобных штуках и при этом реализуют что-то большее чем обработчик очереди.
И как эти проекты себя вели? Ничего не сломалось при переходе? Просто помнится Фабьен как-то говорил на конференции про проблему перерождения всего приложения на каждом реквесте и о том, что хочет ее решить. Но ReactPHP вроде как не упоминал.
Есть примеры кода, для таких связок?
marcjschmidt.de/blog/2014/02/08/php-high-performance.html + www.reddit.com/r/PHP/comments/1yzcvi/bring_high_performance_into_your_php_app_with/
github.com/reactphp/espresso — Silex + ReactPHP
github.com/Blackshawk/SymfonyReactorBundle — дальше простого эксперемента дело не ушло.

Так же видел куски реализаций пулов для доктрины, но там еще pthreads были примешаны и в опенсурсе подобного нет.

Так же подобные штуки не для всех. То есть, сразу возникает необходимость разруливать конкурентные запросы, или делать надстройки для контейнера зависимостей (не всем сервисам можно жить вечно, некоторые должны умирать что бы ничего не поломалось). Словом… проблем явно хватает и что бы сделать возможным реализовать свое SAPI на уровень выше (на уровне httpkernel а не на уровне php) нужно оочень много работать.
phpDaemon умер так и не родившись, имхо. Нынче моден React ( reactphp.org/ ) у которого и исходники получше и работать он умеет не только с libevent ;)

З.Ы. пока читал статью Festor успел первым упомянуть реакт.
На сколько вижу по сорсам, только с либевент и умеет (не считая различных реализаций). stream_select не даёт асинхронности.
Как и говорил в своей статье на тему реакта, либевент, на мой взгляд, костыль! особенно учитывая что в php5.5 есть генераторы.

На данный момент склонен думать, что phthreads — наилучшее решение для многопоточности в PHP.
libevent/libev это event-loop а не многопоточность, там главная идея минимизировать потери по времени между обращениями к i/o. То есть цели распаралелить выполнение кода никто не ставит — только мультиплексирование I/O. Так же это дает преимущество — меньше локов так как в один момент времени доступ к общим ресурсам имеет только кто-то один.

Есть так же довольно популярная идея по использованию нескольких event-looop-в внутри нескольких процессов/потоков воркеров (процессы надежнее так как при падении не потянут за собой все). Насколько я помню что-то подобное применяется в том же nginx. Но это уже порядочно так усложняет архитектуру приложения. Пара лишних локов и производительность сервера будет ниже чем у реализации на libev.
Видимо, не достаточно связанно изложил мысль…
Я не говорил что libev это потоки, наоборот, намекнул, что это async выполнение (упомянув stream_select). А прыгнул я к потокам, т.к. большинство ресурсов в PHP являются блокирующими, и с async тут ловить нечего, ну или использовать, что то типа DNode.
Поэтому и рекомендуется использовать неблокирующие стримы и разруливать все через stream_select внутри. А что бы вручную не разруливать и нужен event-loop и колбэки. А там уже и промисы что бы сума не сойти.

Ну и опять же — можно это все комбинировать. При большом уровне конкуренси потоки будут сильно хуже event-loop.
Ну ресурсы этож не только стримы, но еще и коннекты к базам, кои в большинстве своём блокирующие.

Комбинировать можно, а нужно? Если говорить про реквест-респонсе, то проще обрабатывать каждый запрос в отдельном потоке и всё. Иначе выйдет мешанина из асинка и потоков…
Если говорить про реквест-респонсе, то проще обрабатывать каждый запрос в отдельном потоке и всё. Иначе выйдет мешанина из асинка и потоков…

При большом уровне конкуренси потоки будут сильно хуже event-loop.


Что по поводу базы данных — обычно так и делают, живет себе отдельно пул потоков, по коннекшену на каждый и разруливается в главном треде. Это в случае если у нас там PDO и штуки типа Doctrine. Скажем с mysqli можно юзать неблокирующие вызовы (да, в php можно работать с базой «асинхронно» из коробки)
Судя по моим тестам — он (phthreads) творит какую-то магию, но ни разу не делает тредов ибо обычный sleep в одном из тредов накрывает все «потоки», включая основной.
Каюсь, лично не использовал. Но не верю, есть пруф?
Win7, php 5.5 x64 TS

Сырцы:
Скрытый текст
<?php
class My extends Thread
{
    public $pid = 0;

    public function run()
    {
        echo 'Thread start #' . $this->pid . "\n";
        sleep(1);
        echo 'Thread end #' . $this->pid . "\n";
    }
}

$time = time();

for ($i=0; $i<4; $i++) {
    $my = new My();
    $my->pid = $i;
    $my->start();
}

echo 'Time ' . (time() - $time) . 's' . "\n";


Результат:
Скрытый текст
Thread start #0
Thread end #0
Thread start #1
Thread end #1
Thread start #2
Thread end #2
Thread start #3
Time 3s
Thread end #3
Fesor авторитетно утверждает что sleep() стопит весь процесс, потому, данный пример некорректен для проверки.
Ничего не авторитетно, это лишь предположение. Так как по итогу слип вызывается из главного потока и стопит все, но я не уверен.
Извините, ну тогда стоило бы отметить, что это предположение.
Ну вот без слипа:
Скрытый текст
<?php
class My extends Thread
{
    public $pid = 0;

    public function run()
    {
        echo 'Thread start #' . $this->pid . "\n";
        ob_start();
        for($i=0; $i<999999; $i++) {
            echo 'num: ' . $i;
        }
        ob_end_clean();
        echo 'Thread end #' . $this->pid . "\n";
    }
}

$time = microtime(true);

for ($i=0; $i<5; $i++) {
    $my = new My();
    $my->pid = $i;
    $my->start();
}

echo 'Time ' . (microtime(true) - $time) . 'ms' . "\n";



В результате такая же картина:
Thread start #0
Thread end #0
Thread start #1
Thread end #1
Thread start #2
Thread end #2
Thread start #3
Thread end #3
Thread start #4
Time 1.370078086853ms
Thread end #4
Буферизацию можно убрать. Я подумал что пых проигнорирует цикл, если внутри нет никаких операций, ан нет, сейчас проверил — он не оптимизирует байткод. Так что если увеличить количество итераций цикла в треде и убрать эхо с буфером — будет тоже самое.
О, я разобрался. Походу Thread позволяет создавать только один единственный дополнительный поток, вот и весь секрет.
Скрытый текст
<?php
class Child extends Thread
{
    public function run()
    {
        $time = microtime(true);
        for($i=0; $i<9999999; $i++) {}
        echo 'Thread time ' . (microtime(true) - $time) . 'ms' . "\n";
    }
}

$time = microtime(true);

$my = new Child();
$my->start();

for ($i=0; $i<9999999; $i++) {}
echo 'Main time ' . (microtime(true) - $time) . 'ms' . "\n";
Main time 0.70203995704651ms
Thread time 0.71104001998901ms
Не должно быть так по идее… надо разбираться
sleep стопит весь процесс. Во всяком случае без кода проблемного сложно придумать что-то другое.
упс, каюсь — не рефрешнул
Любопытно почему в README у них написано php5.5+ а в composer.json — 5.4.0+

В целом реализация крайне любопытная но пока молодая и сырая.
На счёт реализации: Наверное стоит упомянуть, что React почти полная адаптация аналогичного решения в node.js.
Под «аналогичным решением» вы подразумеваете обычный event-loop?
На работе соорудил чатик в локалке на Racthet (ну потому что половина на аське, половина в почте, половина в скайпе и т.д., а тут просто говоришь «в чатик зайди» и всё), показал исходники коллеге, а он будучи знаком с нодой — похвастался, мол архитектура, почти все классы и идеи повторяют ноду.

Яж не просто так столь уклончиво выразился «аналогичного решения», ибо реальным пруфом не обладаю, увы. Может кто-то из сообщества ткнёт носом в ссылку по ноде — был бы благодарен.
Node.js внутри использует libev, сама концепция event-loop довольно старая. Что до архитектуры чатика — есть различия так как php не js и некоторые вещи так же реализовать в прицнипе не выйдет. Но в целом… пишите вы чатик на php, js или erlang/go — будет примерно одинаково с точки зрения архитектуры. Тут как бы много чего не придумать. Это ж чатики.
Помимо эвентлупа есть ещё много всякого, идея стримов (когда один можно вкладывать в другой, а тот в третий), те же промизы (о которых упоминали ниже), именование классов идентичное, методы и коллбеки всякие. Вон, про тот же phpDaemon не скажешь, что он слит с ноды.

З.Ы.Кусок композера из React — тоже использует libev:
{
    "suggest": {
        "ext-libevent": ">=0.1.0",
        "ext-event": "~1.0",
        "ext-libev": "*"
    }
}


Стримы используют все, кто знает чем они хороши. А хороши они тем что позволяют организовать единый интерфейс для взаимодействия с i/o. И не важно, сокеты внутри или файлы или еще что.

Про libev я о том и говорил что одна из реализация event-loop (там есть и просто на php и на старом добром libevent) в ReactPhp использует ту же библиотеку на которой построен node.js. Скажем в Python мире есть gevent который так же построен на libev, можете посмотреть их реализацию.

Но сама идея ReactPHP была в построении приложений схожих с node.js приложениями, так что я не спорю что многие идеи черпались у него. Хотя я не согласен что эти идеи принадлежат именно node.js.
Они даже промисы реализовали под php. Так что с ноды передрано чуть менее чем полностью.
Ну промисы слизали из Haskell справедливости ради, а может даже не у них. Эта идея основана на монадах.
По ссылке же написано «The downside of this approach is the latency caused by the spawn of a new PHP interpreter at each request», то есть как обычно на каждый запрос запускается интерпретатор
Вопрос был о другом
Свойство сохранять состояние между запросами? Сдается мне, что если использовать Python, как CGI-скрипт, то он ничем не будет отличаться от PHP или даже C. С другой стороны пары uWSGI + Python и php-fpm + PHP являются весьма похожими конфигурациями (т.н. сервера приложений). Но первая сохраняет свое состояние, а вторая — нет. Как будет себя вести PHP с uWSGI я не проверял, но по логике вещей, он должен вести себя также, как и с php-fpm, т.е. сбрасывать интерпретатор от запроса к запросу.
Но ведь к ЯП это никакого отношения не имеет? Это уже middleware. Там выше уже написали, что и под PHP есть веб-сервер, позволяющий хранить состояния.
К спецификации ЯП это наверное отношения не имеет. А вот к конкретной реализации ЯП это имеет большое отношение. У реализации PHP, например, есть свои особенности (см. линк) и в итоге для PHP оказывается неестественно сохранять свое состояние от запроса к запросу или же использоваться как язык для разработки долгоживущих демонов.
Собственно по поводу сохранения состояния между запросами. Так реализован сам интерпретатор что не может. Запуская код (для php по сути «скрипт») — он инициализирует окружение (собственно его начальное состояние с загруженными extensions, конфигурацией из php.ini, а так же со всеми переменными $_SERVER, $_REQUEST и прочим) и потом начинает выполнение самого кода. Так вот, если выполнить инициализацию окружения повторно (даже не убивая сам процесс) — почистится все наглухо (так и работает fpm — обработал запрос, почистил, обработал запрос, ...). Если же этого не сделать, а просто запустить другой (или этот же) — отвалится часть окружения, конфигурация из php.ini, extensions, в том числе и необходимые переменные для работы «скрипта» (тот же $_REQUEST).
Тут дело не в языке, дело в реализации интерпретатора
Напимер те же extension имеют вхождения .MINIT и .MSHUTDOWN (в названиях могу путаться), но суть сохраняется. MINIT вызывается при инициализации, MSHUTDOWN при выгрузке модуля. Обычно в MINIT делают всякие malloc и обнудения переменных, в MSHUTDOWN free. Ну это так, по простому. Инициализация с сброс окружения вызывают соответственно MINIT и MSHUTDOWN каждого расширения. Ну если окружение не сбросить — память потечет во всех расширениях, если не сделать инициализацию — будут segfault-ы. Вот он и порочный круг php — инициализация, выполнения, сброс. Это зашито в архитектуре.
Аналогично и с php.ini и с прочим.
Вы в исходниках сами разбирались или какую-то литературу читали на данную тему? Хочется в вопросе разобраться поглубже. До этой статьи не задумывался о таких вещах даже.
Качайте сорсы, в них многое понятно, если есть опыт работы с C. Книг обосо хороших я не нашел. Только вот эта, но на мой взгляд много воды.
Начал с ковыряния в расширениях php, потом решил сделать собственный SAPI для php. Собственно горел идеей сделать php без сброса окружения, и как оказалось сделать это крайне сложно, ибо придется переделывать архитектуру самово интерпитатора. Забил на это занятие.

А информация, да, из сырцов. Там все довольно просто, кроме того, что все раскидано по файлам хаотично.
В статье не указано, что не все web-приложения на Python сводятся к WSGI. Контрпример — приложения, основанные на фреймворке Tornado. Там становится неверным утверждение «каждый HTTP-запрос обрабатывается в отдельном потоке».

Но наиболее распространенный сценарий описан верно.
Да, я сознательно остановился только на WSGI приложениях, чтобы сравнить их со схожей моделью FastCGI PHP.
GIL — в один момент времени выполняется только один поток
Не совсем так, лучше сказать, что в один момент работает только один питон код. При этом IO или вызовы расширений (написанных например на С) будут работать параллельно. Например можно загрузить все ядра одним процессом где в разных потоках запустить распаковку/запаковку данных.
Спасибо за поправку. Нужно мне еще покурить этот ваш GIL, чтобы разобраться в нем как следует.
>Если же приложение достаточно большое и обладает внушительным количеством сервисов, то после обработки определенного числа HTTP-запросов, подавляющее большинство сервисов окажется проинициализировнными и находящимися в памяти процесса. Выглядит так, что это может оказаться серьезной проблемой.

Не секрет, что в большинстве случае инициализированные сервисы используются постоянно, а не только какими-то редкими вызовами. И никто не запрещает использовать для таких случаев threadlocal инстансы. Вот это и будет золотой серединой — бОльшая сервисов инициализируется сразу и являются долгоживущими, а какие-то создаются как threadlocal.
Отлично! А есть на примете примеры приложений, на которых можно было бы увидеть использование такого подхода? К сожалению те web-приложения, которые я успел просмотреть на github не тянут на «большие» и грешат, грубо говоря, бизнес-логикой в контроллерах. Уровнем сревисов там зачастую и не пахнет.
Я такой подход я видел в одном крупном закрытом приложении, про публичные приложения не подскажу, так как все обычно ровно как вы описываете.
Теоретически вот это вполне тянет на большое с сервисным уровнем: code.edx.org/
Популярные микро и не очень фреймворки Flask, Bottle, Pyramid и т.д. используют этот подход, чем серьезно улучшают качество жизни python веб-программистов)
Недавно наваял WSGI микро-фреймворк на Python: marnadi.

PS: так же как и вы, перешел в свое время с PHP на Python, о чем не жалею нисколько
n += 1 является именно атомарной операцией, по крайней мере для int и float. Не стоит писать неверные утверждения, это запутывает.

Для пользовательских типов данных — да, чаще всего будет неатомарно.
Так, а как же тогда интерпретировать результаты дизассемблирования? Ни коим образом не спорю, просто действительно хочу узнать истину.
Смотрим на дизассемблер:
In [1]: import dis

In [2]: def f(a, b):
   ...:     a += b
   ...:     

In [3]: dis.dis(f)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 INPLACE_ADD
              7 STORE_FAST               0 (a)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

Нужная нам инструкция — это INPLACE_ADD. Один opcode, значит переключения GIL не происходит.
Здесь INPLACE_ADD будет делать ровно то же самое, что и BINARY_ADD. Опкодов два: INPLACE_ADD и STORE_FAST. Уберите STORE_FAST и получите, что ничего не изменилось.

Это не список, у которого определён __iadd__. Это неизменяемое целое.
Ну, то есть, не здесь, а у Ostrovski. Целое у него.
По этой же причине нельзя забывать про LOAD_GLOBAL (у вас его нету). Если значение n изменится после LOAD_GLOBAL, но перед STORE_GLOBAL, то результат будет некорректным. А это три промежутка, куда можно вклинится.
Бородатые Python-разработчики строят свои приложения на фреймворках. Низкоуровневые аппликухи — это то, за что проект-манагер должен бить по рукам, потому что столь низкоуровневое программирование есть просто потеря времени. Нужна сложность и мощь? Django. Нужна высокая скорость, хайлоуд и прочее? Tornado. Нужно что-то простое, не городя огород? Flask, bottlepy и прочие.
Возможно я невнимательно смотрел, но что-то во flask я не заметил уровня, где можно было бы разместить пользовательские сервисы. Фреймворк для меня скорее выступает как точка входа в приложение, предоставляя такой базовый функционал, как роутинг и шаблонизация, например. И я стараюсь избегать влияния фреймворка на архитектуру приложения, если оно чуть сложнее, чем персональная страничка.
Канонический способ сделать там такое, это написать расширение. На примере как раз и рассматривается подключение к БД.
И я стараюсь избегать влияния фреймворка на архитектуру приложения, если оно чуть сложнее, чем персональная страничка.
А фреймворк разве не по определению жестко задает структуру приложения, во всяком случае на низших уровнях?
Именно по определению фреймворк скорее всего задает структуру приложения. С другой стороны фреймворки должны удовлетворять массовому спросу, позволяя ускорять разработку типовых решений. Я же придерживаюсь мнения, что если приложение чуть менее, чем стандартное, то лучше его сделать максимально обособленным от фреймворка, оставив за последним право быть точками входа в это самое приложение. Тогда имеешь куда больший контроль над системой и не приходится «бороться» с фреймворком.
Не знаю, имеет ли смысл углубляться в дебри для приложения чуть сложнее чем персональная страничка. Тем более, что Flask. Bottle, etc — не зря называются микрофреймворками. Они собственно только и делаю, что прячут «дебри», и «наворачивают» не так сильно всего, как полные фреймворки. Если переводить на пиэчпишные понятия, то они скорее коррелируют в этом плане со Silex. При желании довести их до уровня Symfony2 возможно написанием расширений. Хотя уровень Symfony2 «из коробки» в питоне тоже представлен, это например Django.
Да, а многопоточность решается gevent и multiprocessing.
Все ошибки используют стандартный механизм исключений и могут быть перехвачены (разве что за исключением SyntaxError).
Как это как это.

In [2]: try:
   ...:   import test
   ...: except Exception as e:
   ...:   pass
   ...: 

In [3]: e
Out[3]: SyntaxError('invalid syntax', ('test.py', 1, 10, 'import def\n'))
Таким же образом можно перехватить SyntaxError в eval, например. А вот в текущем файле — врядли, так как интерпретатор парсит файл целиком, и, если ошибка синтаксиса внутри блока try, ее все равно не удасться перехватить, так как в общем случае не известно, как обрабатывать следующий за ней код.
Вы хотите странного. Нет никакого «следующего за синтаксической ошибкой кода», весь код который следует за ошибкой — не верен и не может быть исполнен при всем желании. Ошибка происходит именно на этапе импорта, там её и можно отловить.
Я как раз не хочу. Просто пояснил свою мысль из статьи по поводу перехвата SyntaxError. Согласен, что код, следующей за синтаксической ошибкой, в общем случае не возможно интерпретировать онднозначно.
Это для Python (и для большинства остальных языков, где есть нечто подобное). В некоторых языках такое поведение будет неожиданным. Правда я из таких знаю только VimL¹, а это никак не образец хорошего внутреннего устройства. Про PHP не знаю.

¹ Ошибки, использующиеся вместо SyntaxError, можно отловить прямо на месте, т.к. в VimL команда всегда заканчивается в конце строки, если только используемый метод получения строк не предоставляет их в виде одной \n‐разделённой C’шной строки (особенность реализации, которую мне придётся поддерживать).
В ведении исправьте пожалуйста «web-зазработки»
Wow, вдохновляющая статья, как раз начал уход от php в сторону python. Посоветуете какую-нибудь литературу на данную тему?
Позвольте поинтересоваться, а в чём причина именно «ухода»?
Причина исключительно личного предпочтения. Меня php развратил. Есть функции и инструменты на все случаи жизни, которые я никак не могу запомнить, но легко могу научиться использовать. В итоге во мне умирает программист. (:
Тогда, думаю Вам стоит переходить на asm… =)
Есть функции и инструменты на все случаи жизни, которые я никак не могу запомнить, но легко могу научиться использовать.
В python с этим еще круче.
UFO just landed and posted this here
Рекомендую «Two scoops of Django. Best practice for Django 1.6»
>за это приходится платить потенциально возможными race condition

Есть глобальное мутабельное состояние — ставь лок. Python тут не уникален.
Нельзя так сравнивать две вещи и писать, что один язык умирающий, а второй универсальный. Помню в середине 90х читая Г. Буча «Объектно-ориентированный дизайн и программирование» сразу же уперся в ряд голословных утверждений о том что ООП лучший подход для всего в программировании.

Все зависит от задачи. Глупо использовать строительный кран когда надо всего-то построить собачью конуру или сарай одноэтажный.

Сравнение должно происходить по заданным критериям. Для чего-то лучше одно, для других задач — другое.
Простите, Вы статью читали?)
Да, там «универсальный язык программирования» еще и с ошибкой написано. Универсальных языков программирования не бывает. Как не бывает универсального инструмента на все случаи жизни.
Когда говорят «универсальный язык программирования» обычно имеют ввиду язык, который
  1. Изначально предназначался для решения широкого спектра задач.
  2. Является Тьюринг‐полным.
  3. На котором при этом люди реально могут решить практически всё — от написания текстового редактора до 3D‐рендеринга. Разумеется, это «всё» они могли бы решить лучше (быстрее и/или с меньшими затратами ресурсов компьютера) на других языках… если бы умели на них писать, причём быстро.
При этом из «всего» на самом деле исключены узкоспециализированные задачи: написание прошивок, скриптование ПО, не поддерживающего язык, …

В общем, данный термин не следует воспринимать совсем уж буквально. Текст или речь, произведённые людьми вообще не стоит воспринимать буквально практически никогда — математические доказательства есть практически единственные тексты, автор которых предполагал или должен был предполагать их буквальную интерпретацию.
Вы точно не бот? Если нет, то все же прочитайте статью, она совсем о другом.
«имеют в виду» пишется в три слова. Остальное словоблудие. Насколько широкого спектра? В чем он измеряется? Решить можно что-то виртуально? Практически не все? Программирование относится к техническим наукам, и тут важна четкость как в математике. А то потом начинают запожные гвозди кувалдой заколачивать. Ради скрипта элементарного фреймворк устанавливают.
Здесь не научная статья. Никому низачем не нужно чёткого определения термина «язык общего назначения». Совсем. Умные люди отлично понимают нечёткие определения, тем более, что в обычном толковом словаре большинство именно такие.
Sign up to leave a comment.

Articles

Change theme settings