19086


FuelPHPのtaskでバッチ処理を行っていたのですが、処理時間がだいぶかかるようになったため、マルチスレッド化して並列処理を行うように改修しているのですが、不可解な挙動をするので、相談させて下さい。

テストスクリプトとして以下を記述しました。

fuel/app/tasks/test.php

[code]

    <?php
    namespace Fuel\Tasks;
    
    class Test
    {
        public static function run()
        {
            // 動く（10がインサートされる）
            \Foo_Bar::insert(10);
    
            $bar = new \Foo_Bar();
    
            // 動く（20がインサートされる）
            $bar->run();
    
            // 動かない（Class 'DB' not found）
            $bar->start();
        }
    }
    
[/code]

fuel/app/classes/foo/bar.php

[code]

    <?php
    class Foo_Bar extends Thread
    {
        public static function insert($i)
        {
            DB::insert('test')->set(array(
                'i' => $i,
            ))->execute();
        }
    
        public function run()
        {
            $this->insert(20);
        }
    }
    
[/code]

挙動は、test.phpのコメントの通りとなります。  
スタティックメソッド呼び出しの際は問題なくテーブルに10がインサートされます。  
また、Foo_Barクラスをインスタンス化して、直接run()メソッドを呼び出しても問題ありません。  
ところが、別スレッドとしてrun()メソッドが呼ばれると、Class 'DB' not foundエラーが発生します。  
self::insert(20); としてみたり、run()メソッド内で直接 DB::insert()を呼び出しても同様のエラーが発生します。

DBアクセス以外の処理（外部クラスを利用しない）は問題なく、マルチスレッドで動くことは確認できています。  
なぜ、マルチスレッドにした場合、外部クラスが見つからなくなるのか、また、どうすれば利用できるのかご教示いただけると幸いです。


CC BY-SA 3.0
2015-10-16T05:19:33.487
17710
2015-11-20T20:25:48.407
10298
question
php fuelphp
FuelPHPのtaskでマルチスレッドを使用すると外部クラスが見つからない


<http://blog.madewithlove.be/post/thread-carefully/>  
オートローダで読み込んだクラスは生成したスレッドに引き継がれないので、スレッド内部でオートローダの読み込みのような初期化を別途行う必要があります

追記：「オートローダで読み込んだクラスは生成したスレッドに引き継がれない」は間違いでした。くわしくはunaristさんの回答を参照。「初期化を別途行う」で動くというのは間違ってもいないので、サンプルを追記しときます。静的プロパティの初期化は必要なのでそこそこ面倒くさいです

[code]

    <?php
    namespace Fuel\Tasks;
    
    class Threadtest
    {
        public static function run()
        {
            for($i=0;$i<5;$i++){
                $pool[] = new ThreadSample(); 
            }
            foreach($pool as $key => $worker){
                echo ($key+1).' started'."\n";
                $worker->start();
            }
            foreach($pool as $worker){
                $worker->join();
            }
        }
    }
    
    class ThreadSample extends \Thread{
        function run(){
            require APPPATH.'bootstrap_thread.php';
            $array = [];
            var_dump(\DB::select()->from('test_table')->execute()->current());
        }
    }
    
[/code]

bootstrap_thread.php はたとえばこんなの↓

[code]

    <?php
    class Autoloader_Threaded extends Fuel\Core\Autoloader{
        protected static $classes = array();
        protected static $namespaces = array();
        protected static $psr_namespaces = array();
        protected static $core_namespaces = array(
            'Fuel\\Core',
        );
        protected static $default_path = null;
        protected static $auto_initialize = null;
    }
    class Config_Threaded extends Fuel\Core\Config {
        public static $loaded_files = array();
        public static $items = array();
        public static $default_check_value;
        protected static $itemcache = array();
    }
    class_alias('Autoloader_Threaded', 'Autoloader');
    class_alias('Config_Threaded', 'Config');
    setup_autoloader();
    require VENDORPATH.'autoload.php';
    \Autoloader::register();
    
[/code]


CC BY-SA 3.0
2015-11-20T10:34:11.533
19086
2015-11-20T20:25:48.407
2015-11-20T20:25:48.407
13349
13349
17710
answer


## 要約

  * pthreads拡張で作ったスレッドでは、クラスの別名やオートローダがリセットされます
  * それとは別に、FuelPHPはpthreads拡張に対応していません

* * *

`DB` クラスの実体は `Fuel\Core\DB` なのですが、FuelPHP の起動時に別名登録を行うことで `DB`
で使えるようになっています。で、これを **新しいスレッドに引き継げない**
ために、クラスが見つからないというエラーになっているようです。クラス定義自体は引き継がれています。

FuelPHP の内部でもこの別名を使っていますから、他のコアクラスも含め、別名を定義しなおす必要があります。幸いこの別名は規則的ですから、Thread か
Worker の `run()` の最初に以下のコードを書けば一括処理できそうです。

[code]

    foreach (get_declared_classes() as $fullname) {
        if (substr($fullname, 0, 10) === 'Fuel\\Core\\') {
            class_alias($fullname, substr($fullname, 10));
        }
    }
    
[/code]

ところが `\Foo_Bar::insert()` や `$bar->run()` を取り除くとやはりエラーになります。
**オートローダ関数のリストも新しいスレッドには引き継がれない**
ようで、オートローディングが使えないのです。そのため、あらかじめクラスを読み込んでおく必要があります。

これに関してはpthreads拡張のIssueにも書かれていました。
<https://github.com/krakjoe/pthreads/issues/68>

・・・というのが「Class xxx not found」の話ですが、（holywise
さんが書かれたように）クラスが新しいスレッドに引き継がれる際、静的プロパティに設定された配列が軒並みnullになります。FuelPHP
内部でもこのような個所が多数あるようで、結局ろくに動きませんでした。

pthreads拡張のオプションでクラスを引き継がないようにして、再度読み込みなおすという手があるにはありますが、`oil`
ファイルに書かれている分も含めFuelPHPの初期化をし直す必要があるので・・・。


CC BY-SA 3.0
2015-11-20T17:55:14.687
19101
2015-11-20T17:55:14.687
8000
17710
answer