Многопоточность и Yii

Общие вопросы по использованию фреймворка. Если не знаете как что-то сделать и это про Yii, вам сюда.
Ответить
Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Многопоточность и Yii

Сообщение DShefer » 2013.12.11, 16:10

Добрый день, форумчане.

Вот уже несколько дней бьюсь с задачей по распараллеливанию расчетов системы.
В качестве multithreading-инструмента решил использовать расширение pthreads.

И, в результате работы столкнулся с такой проблемой, что при попытке обращения к любому объекту, классу или полю из треда вылезает ошибка такого типа:

Код: Выделить всё

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130

Fatal error: Class 'CException' not found in C:\OpenServer\domains\local.dev\common\lib\Yii\base\CComponent.php on line 130
PHP Error [8]

Trying to get property of non-object (C:\OpenServer\domains\local.dev\common\lib\Yii\db\ar\CActiveRecord.php:658) 
При попытке создать копию приложения Yii в каждом треде, получаем такой вариант ошибок:

Код: Выделить всё

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388

Fatal error: Cannot access property Yii::$_aliases in C:\OpenServer\domains\local.dev\common\lib\Yii\YiiBase.php on line 388
PHP Error [8]

Trying to get property of non-object (C:\OpenServer\domains\local.dev\common\lib\Yii\db\ar\CActiveRecord.php:658)
Yii-приложение создается как singletone. Есть подозрение, что причина кроется в этом.
Но тогда возникает логичный вопрос: как "научить" треды всему содержимому проекта?

То, что пробовал:
- засылать в тред clone Yii::app();
- создавать Yii-приложение внутри треда;
- отправлять само приложение, как переменную в класс;
- просто пытаться стучаться к объектам Yii;

Если кто сталкивался и решал подобные проблемы, поделитесь, пожалуйста, опытом.

Заранее спасибо за ответы.


P.S. Работоспособность pthreads я проверил отдельно, действительно, библиотека работает. Потоки создаются и считают что нужно.
Чем дальше в лес, тем важнее GPS.

ApJIeKuHo
Сообщения: 191
Зарегистрирован: 2011.09.28, 11:21

Re: Многопоточность и Yii

Сообщение ApJIeKuHo » 2013.12.11, 16:20

Если ТС решит проблему сам - просьба сообщить как) Тоже интересует данный вопрос)
Стучитесь да откроется Вам, Молитесь да услышаны будете!
P.S.: Yii Jabber Conference: yii@conference.jabber.ru

Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Re: Многопоточность и Yii

Сообщение DShefer » 2013.12.11, 16:24

Отказался пока от тредов. Начинаю ковырять pcntl_fork.
Менее приятная вещь, плодит кучу процессов, что скажется не весьма в высоконагруженных системах.
Чем дальше в лес, тем важнее GPS.

Faster
Сообщения: 139
Зарегистрирован: 2013.09.19, 14:23

Re: Многопоточность и Yii

Сообщение Faster » 2013.12.11, 17:33

стесняюсь спросить, задача каждого потока какова?

Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Re: Многопоточность и Yii

Сообщение DShefer » 2013.12.11, 20:23

Faster писал(а):стесняюсь спросить, задача каждого потока какова?
Каждый поток вызывает абстрактный метод класса Robot, который делает расчет, используя как формулы в коде, так и подтягивая данные из мускуля. Причем каждый поток, это подключение класса, который екстендит класс Робот, где содержаться все абстракции.

Постарался ответить максимально развернуто.
Чем дальше в лес, тем важнее GPS.

Faster
Сообщения: 139
Зарегистрирован: 2013.09.19, 14:23

Re: Многопоточность и Yii

Сообщение Faster » 2013.12.11, 20:42

то есть каждый поток автономен?

ApJIeKuHo
Сообщения: 191
Зарегистрирован: 2011.09.28, 11:21

Re: Многопоточность и Yii

Сообщение ApJIeKuHo » 2013.12.11, 21:24

Хм.. получается что да...
Стучитесь да откроется Вам, Молитесь да услышаны будете!
P.S.: Yii Jabber Conference: yii@conference.jabber.ru

Faster
Сообщения: 139
Зарегистрирован: 2013.09.19, 14:23

Re: Многопоточность и Yii

Сообщение Faster » 2013.12.11, 21:36

альтернативное решение форкам - запускать поток в цикле

Код: Выделить всё

passthru('('.__PHP.' -f '.PATH_DIR.'thread.php '.$id.' & ) >> /dev/null 2>&1');
 
количество потоков регулировать например по количеству флагов завершения в БД (поток запустился, флаг обновился, поток завершен другой флаг обновился)

очевидный недостаток - у www юзера должны быть права на выполнение команды, если из консоли то вообще без проблем

Аватара пользователя
anton44eg
Сообщения: 2716
Зарегистрирован: 2012.01.25, 13:37
Откуда: Киев

Re: Многопоточность и Yii

Сообщение anton44eg » 2013.12.12, 20:25

познакомьтесь с очередями и воркерами

Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Re: Многопоточность и Yii

Сообщение DShefer » 2013.12.13, 10:14

anton44eg писал(а):познакомьтесь с очередями и воркерами
Информативность зашкаливает) Спасибо, кэп!)

Использование любых очередей для распараллеливания процессов - абсолютно бессмысленное дело.
Сервер очередей (а-ля Герман) хорошо использовать для исполнения фоновых, а не параллельных процессов.

По теме. Попробовал форкать вызов изнутри приложения. Yii ругается на функцию pcntl_fork(), он её не знает. Точнее её не кушает апач.

Форкать нужно в CLI режиме. Решено было работать через консольные команды, и тут наткнулся на такую штуку. Обмен данными реализую через мемкэш.
Помещаю входные данные в кеш под ключом в приложении. При попытке вытащить по этому ключу, получаю false.

Логичный вопрос: какого рожна?

Кто сталкивался, прошу помочь.
Заранее спасибо.
Чем дальше в лес, тем важнее GPS.

Faster
Сообщения: 139
Зарегистрирован: 2013.09.19, 14:23

Re: Многопоточность и Yii

Сообщение Faster » 2013.12.13, 11:10

в каком виде данные?
expire time какой то указывается?

Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Re: Многопоточность и Yii

Сообщение DShefer » 2013.12.13, 12:07

Данные в виде массива объектов, чисел, строк.
Короче много чего, знатная каша.

По поводу expire_time пока не думал. Сперва нужно реализовать функционал.
Пытаюсь использовать вместо мемкеш монгу.
Чем дальше в лес, тем важнее GPS.

Аватара пользователя
anton44eg
Сообщения: 2716
Зарегистрирован: 2012.01.25, 13:37
Откуда: Киев

Re: Многопоточность и Yii

Сообщение anton44eg » 2013.12.13, 20:16

DShefer писал(а):
anton44eg писал(а):познакомьтесь с очередями и воркерами
Информативность зашкаливает) Спасибо, кэп!)

Использование любых очередей для распараллеливания процессов - абсолютно бессмысленное дело.
Сервер очередей (а-ля Герман) хорошо использовать для исполнения фоновых, а не параллельных процессов.
Хм, голословно. Слышали про горизонтальное масштабирование?

Аватара пользователя
DShefer
Сообщения: 6
Зарегистрирован: 2013.12.11, 15:32

Re: Многопоточность и Yii

Сообщение DShefer » 2013.12.18, 10:11

Да, слышал. Запуск выполнения многих консольных команд в фоне. Демон, типа германа, все это выполняет.

Проблема в том, что каждый запуск - это поднятие отдельного yii приложения. Это очень ресурсозатратно, даже непозволительно, для высоконагруженных систем.
В идеале, я ищу способ, чтобы внутри yii распараллелить обработку. Хотя это, конечно, вряд ли.

Ну или менее затратный по ресурсам способ.
Чем дальше в лес, тем важнее GPS.

Аватара пользователя
anton44eg
Сообщения: 2716
Зарегистрирован: 2012.01.25, 13:37
Откуда: Киев

Re: Многопоточность и Yii

Сообщение anton44eg » 2013.12.18, 11:53

не обязательно запускать каждый раз. создаете комманду, внутри она слушает очередь, при новых сообщениях - выполняет, отсылает результат и дальше слушает. запускаете нужное количество консольных приложений и живете спокойно. понадобилось больше мощности - втыкнули + 1 сервер и все снова хорошо

ApJIeKuHo
Сообщения: 191
Зарегистрирован: 2011.09.28, 11:21

Re: Многопоточность и Yii

Сообщение ApJIeKuHo » 2013.12.19, 23:04

эм... а можно пример для тех кто это только начинает осваивать?
Стучитесь да откроется Вам, Молитесь да услышаны будете!
P.S.: Yii Jabber Conference: yii@conference.jabber.ru

Great
Сообщения: 70
Зарегистрирован: 2011.07.27, 11:31

Re: Многопоточность и Yii

Сообщение Great » 2014.03.13, 19:33

Столкнулся с похожей проблемой и очень хотелось её решить, т.к. уже много времени потрачено на освоение pthreads и реализацию задумки. В итоге проблему удалось обойти, хоть и с небольшими компромиссами, но удалось получить полный функционал Yii в треде.

Я не профи в мультипроцессности, так что объяснять буду как понимаю (а понимаю я не все) и как работает на практике. И еще: все описываемое справедливо только для console app. Использовать мультитрединг в web app имхо верх безумства.

Для начала почему не работает как есть. При вызове Thread::start() создается отдельный контекст выполнения (или окружение - как хотите). Но он не содержит копии всех объектов, которые были у родителя - видимо, это очень накладно с т.з. производительности. Вместо этого он наследует таблицы классов, инклудов, функций и т.д., полный список см. здесь: http://ru2.php.net/manual/en/pthreads.constants.php. До есть дочерний процесс знает, что есть класс Yii, можно использовать его методы (например, Yii::getPathOfAlias() работает), однако Yii::app() возвращает null - а это значит, что CCondoleApplication не инициализирован. Из-за того, что это синглтон, объект класса CConsoleApplication не является частью таблицы классов, а потому дочерний процесс его и не понимает. Здесь и начинаются проблемы. Пересоздать app() в дочернем процессе нельзя, т.к. Yii активно использует константы, а они также унаследованы от родителя, так что процесс инициализации не будет успешным. Можно отменить наследование констант, но увы, и этого будет мало (там возникнут другие проблемы).

Путем долгих экспериментов, проб и ошибок, я пришел к следующему рецепту:

1. Родительский тред должен запускаться в standalone файле, а не с помощью php yiic.php controller action. Это основной компромисс, но он важный. Код родителя не должен использовать Yii, юзайте нативный PHP. Все, что вам нужно от Yii можно будет потом использовать в тредах.
Далее пример такого файла:

Код: Выделить всё

echo "Starting\n";
$storage = new GlobalStorage();  // GlobalStorage() у меня это Stackable, который используется для обмена данными между тредами
$storage->init();

$listenerThread = new Listener( $storage );
// Насоледуем все, кроме констант и include'ов. Хотя в принципе родительское окружение и так чистое, но на всякий..
if ( $listenerThread->start( PTHREADS_INHERIT_ALL & ~PTHREADS_INHERIT_CONSTANTS & ~PTHREADS_INHERIT_INCLUDES ) === false )
    die( 'Unable to launch Listener thread' );

// Запускаем цикл, который будет поддерживать жизнь родительского треда, в нем можно, например, проверять жив ли дочерний, писать статистику, ну или просто спать. Тут код чисто для примера.
while( 1 )
{
    if ( $listenerThread->isRunning() === false )
    {
        echo 'Listener is not started';
        $listenerThread = new Listener( $storage );
        $listenerThread->start();
    }
    else
    {
        echo "Thread is still alive\n";
    }

        // А еще тут можно чистить Pool, например..
    if ( $storage->pool instanceof Pool )
    {
        echo 'Collecting free objects' . "\n";
        $storage->pool->collect( function( $work )
        {
            return $work->isComplete();
        });
    }
    sleep( 60 );
}
 
2. В дочернем треде инициализируем Yii. По сути нам нужно только окружение, обработчик (метод run()) нам не нужен. Поэтому я переписал yiic.php, назвав новый файлик yiicnts.php (non thread safe типа).

Код: Выделить всё

$config=dirname(__FILE__).'/config/console.php';

defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
defined('YII_DEBUG') or define('YII_DEBUG',true);

require_once(dirname(__FILE__).'/../../yii-1.1.14/yii.php');

$app=Yii::createConsoleApplication($config);
$app->commandRunner->addCommands(YII_PATH.'/cli/commands');

$env=@getenv('YII_CONSOLE_COMMANDS');
if(!empty($env))
    $app->commandRunner->addCommands($env);
 
3. А теперь осталось написать метод run() у треда:

Код: Выделить всё

public function run()
{
  $yiic=dirname( __FILE__ ) . '/../yiicts.php';
  include_once( $yiic );
  var_dump( Yii::app() );  // Работает!
  // Далее полезный код, использующий Yii на всю.
}
 
Сам файлик у меня пока остался в commands/, хотя это конечно теперь не лучшее место для него. Для запуска нужно использовать php /path/to/command/dir/SomeCommand.php .

В общем вот такое решение.. Привел лишь очень частный пример, который конечно нужно будет сильно допиливать под конкретные нужны, но в данном случае цель была - донести идею. Буду рад, если кто-то предложит лучшие варианты. Детального тестирования описываемого подхода я еще не проводил, все еще продолжаю разработку, но, надеюсь, кому-нибудь это описание поможет или натолкнет на новые идеи. Удачи!

Great
Сообщения: 70
Зарегистрирован: 2011.07.27, 11:31

Re: Многопоточность и Yii

Сообщение Great » 2014.03.13, 20:06

С воркерами такая схема также работает. Причем инициализировав Yii в воркере, им можно спокойно пользоваться в Stackable, который сейчас исполняется в воркере. Получается вполне удобно.

С другой стороны нужно иметь ввиду, что воркер наследует то окружение, где он запущен. То есть если, например, запустить воркер в треде, где инициализирован Yii - столкнемся с теми же проблемами, с которых начали.

Также, при использовании Pool пока нет возможности указать флаги инициализации воркера (видимо, из пула воркеры всегда запускаются с PTHREADS_INHERIT_ALL).

Ответить