简体   繁体   English

PHP - 多线程和池

[英]PHP - Multi threading and pools

I am using Pool object in PHP pthread , and made the following test script, to see how the pooling should work.我在 PHP pthread使用Pool对象,并制作了以下测试脚本,以查看池应如何工作。 I tought, that what pooling should do, is to get a given number of tasks, open up a maximum x number of workers, and assign them the tasks, and as soon as a worker finishes with a task, if more tasks are available, assign to that worker a new task.我认为,池化应该做的是获取给定数量的任务,打开最多x数量的工作人员,并为他们分配任务,一旦工作人员完成任务,如果有更多任务可用,为该工人分配一项新任务。

Given the below example, and the above assumption:鉴于以下示例和上述假设:

class Work extends Threaded {
    public $id;

    public function __construct($id) {
        $this->id = $id;
    }

    public function run() {
        if ($this->id == 0) {
            sleep(3);
            echo $this->id . " is ready\n";
            return;
        } else {
            echo $this->id . " is ready\n";
            return;
        }
    }
}

$pool = new Pool(2, 'Worker', []);
for ($i=0; $i<4; $i++) $pool->submit(new Work($i));
while ($pool->collect());
$pool->shutdown();

I was expecting this script to output the following information:我期待这个脚本输出以下信息:

1 is ready 1 准备好了
2 is ready 2 准备好了
3 is ready 3 准备好了
0 is ready 0 准备好了

because, there are essentially 2 workers available, and because of the sleep operatin the first worker stumbles upon, task 1,2,3 must be completed by the second worker.因为,基本上有 2 个工人可用,并且由于第一个工人偶然发现了sleep操作,任务 1、2、3 必须由第二个工人完成。

Instead of this, the output I am getting is:而不是这个,我得到的输出是:

1 is ready 1 准备好了
3 is ready 3 准备好了
0 is ready 0 准备好了
2 is ready 2 准备好了

It is clear, that worker 1, gets assigned job 0, and job 2 at the get go, thus worker 2, after finishing job 1 and 3, just waits, instead of taking over job 2 from worker 1.很明显,工人 1 一开始就被分配了工作 0 和工作 2,因此工人 2 在完成工作 1 和 3 后,只是等待,而不是从工人 1 手中接过工作 2。

Is this a bug?这是一个错误吗? Or is this intended to work this way?或者这是打算以这种方式工作?

My PHP version:我的 PHP 版本:

PHP 7.2.14 (cli) (built: Jan  9 2019 22:23:26) ( ZTS MSVC15 (Visual C++ 2017) x64 )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

For some reason my Docker has crapped itself now that I've updated Windows to 1809, so posting untested.出于某种原因,我的 Docker 已经崩溃了,因为我已经将 Windows 更新到 1809,所以发布未经测试。 (So sorry, no output to give atm) (很抱歉,没有输出给 atm)


Modified existing code I use in a project with your counter + sleep.修改了我在您的计数器 + 睡眠的项目中使用的现有代码。

$pool = new Pool(2);
foreach ([0,1,2,3] as $count) {
    $pool->submit(
        new class ($count) extends Threaded
        {
            private $count;

            public function __construct(int $count)
            {
                $this->count= $count;
            }

            public function run()
            {
                if ($this->count== 0) {
                    sleep(3);
                    echo $this->count . " is ready\n";
                } else {
                    echo $this->count . " is ready\n";
                }
            }
        }
    );
}

while ($pool->collect());

$pool->shutdown();

I use anonymous class ( new class ($count) extends Threaded ) as the submit() param.我使用匿名类( new class ($count) extends Threaded )作为submit()参数。

On the server this runs perfectly, using a Docker instance running PHP ZTS 7.2.13 on Alpine 3.8在服务器上运行完美,使用在 Alpine 3.8 上运行 PHP ZTS 7.2.13 的 Docker 实例

Let me answer: from what I know about pthreads in php, pool is like number of proccessing php.exe that can be run at the same times.让我回答:根据我对 php 中 pthreads 的了解,pool 就像可以同时运行的处理 php.exe 的数量。

So in your case, you define two pool by using new Pool(2, 'Worker', []);因此,在您的情况下,您可以使用new Pool(2, 'Worker', []);定义两个池new Pool(2, 'Worker', []);

So let's make abstract explanation about it.所以让我们对它做一个抽象的解释。 There is 2 Pool, call it as PoolA and PoolB .有 2 个 Pool,称为PoolAPoolB

Loop from 0 to 3, each loop submit task to Pool.从 0 到 3 循环,每个循环向 Pool 提交任务。

There are 4 tasks from 0 to 3, lets call them by task0 , task1 , task2 , task3 .有从0至3 4个任务,允许通过打电话给他们task0task1task2task3

When loop occur, from my perspective, it should be queue like this当循环发生时,从我的角度来看,它应该像这样排队

PoolA -> submit task0
PoolB -> submit task1
PoolA -> submit task2
PoolB -> submit task3

But from class Work that will be task0, ... till task3.但是从class Work将是 task0,...直到 task3。

Situation/Condition情况/条件

You define some logic in run() => when parameter(in this case $id from constructor) is 0, then sleep(3) .您在 run() => 中定义一些逻辑,当参数(在本例中为 $id 来自构造函数)为 0 时,则为sleep(3)

From this situation, PoolA is which submit task0 that contains parameter($id) is value 0, PoolA will wait for 3 seconds.在这种情况下, PoolA是哪个提交task0包含参数($id)的值为 0, PoolA将等待 3 秒。 PoolA also submit task2 . PoolA也提交task2

On the other hand, PoolB submit task1 and task3 , from this situation, doesn't need to wait for 3 seconds.在另一方面, PoolB提交task1task3 ,从这种情况下,并不需要等待3秒。

So when while($pool->collect());所以当while($pool->collect()); is run, possible queue that's most likely happen正在运行,最有可能发生的可能队列

task1    (PoolB)
task3    (PoolB)
task0    (PoolA)  ->>>> PoolA delayed because from task0 needs to sleep for 3 seconds
task2    (PoolA)

So I think it's correct when outputs are所以我认为输出是正确的

1 is ready 1 准备好了
3 is ready 3 准备好了
0 is ready 0 准备好了
2 is ready 2 准备好了

There is a questions.有一个问题。

Why is only PoolA that delayed, even if PoolA delayed why task2 didn't submit to PoolB or why task1 or task3 not submit to PoolA??为什么只有 PoolA 延迟了,即使 PoolA 延迟了为什么 task2 没有提交到 PoolB 或者为什么 task1 或 task3 没有提交到 PoolA??

Well, I don't understand too.嗯,我也不明白。 I have task similar to yours, after many experiments, I'm not sure pthreads which use Pool & Threaded is multi-threading or multiprocessing .我有与您类似的任务,经过多次实验,我不确定使用Pool & Threaded pthreadsmulti-threading or multiprocessing

Echoing from the individual threads can be deceiving.从单个线程回声可能是骗人的。 I often find that they seem like they are executing before they are even called.我经常发现他们似乎在被调用之前就已经执行了。 I'd recommend avoiding echoing from inside threads, unless you don't care about the order, as it can be still be useful to test for specific circumstances, etc.我建议避免从内部线程回显,除非您不关心顺序,因为它仍然可以用于测试特定情况等。

Below is some code which should resolve any questions of when the code is executing, as this code sorts the results by the actual time they executed.下面是一些应该解决代码执行时间的任何问题的代码,因为此代码按实际执行时间对结果进行排序。 (It's also a nice example of how to get results back from a thread pool.) (这也是如何从线程池中取回结果的一个很好的例子。)

<?php
class Work extends Threaded {
    public $id;
    public $data;
    private $complete = false;
    public function __construct($id) {
        $this->id = $id;
    }

    public function run() {
        $temp = array();
        if ($this->id == 0) {
            echo "<pre>".$this->id . " started (from inside threaded)";
            $temp[] = array(microtime(true), $this->id . " started");
            sleep(3);
        }
        echo "<pre>".$this->id . " is ready (from inside threaded)";
        $temp[] = array(microtime(true), $this->id . " is ready");
        $this->data = (array) $temp; // note: it's important to cast as array, otherwise you will get a volitile
        $this->complete = true;
    }

    public function isDone() {
        return $this->complete;
    }
}

// we create a custom pool, to pass on our results
class ExamplePool extends Pool {
    public $dataAr = array(); // used to return data after we're done
    private $numTasks = 0; // counter used to know when we're done
    private $numCompleted = 0; // keep track of how many threads finished
    /**
     * override the submit function from the parent
     * to keep track of our jobs
     */
    public function submit(Threaded $task) {
        $this->numTasks++;
        parent::submit($task);
    }
    /**
     * used to wait until all workers are done
     */
    public function process() {
        // Run this loop as long as we have
        // jobs in the pool
        while ($this->numCompleted < $this->numTasks) {
            $this->collect(function (Work $task) {
                // If a task was marked as done, collect its results
                if ($task->isDone()) {
                    //this is how you get your completed data back out [accessed by $pool->process()]
                    $this->dataAr = array_merge($this->dataAr, $task->data);
                    $this->numCompleted++;
                }
                return $task->isDone();
            });
        }
        // All jobs are done
        // we can shutdown the pool
        $this->shutdown();
        return $this->dataAr;
    }
}

$pool = new ExamplePool(4);
for($i=0; $i<4; $i++) { 
    $pool->submit(new Work($i));
}
$retArr = $pool->process();
usort($retArr, 'sortResultsByTime'); // sort the results by time

// echo out the sorted results
echo "<br><br>";
for($i=0;$i<count($retArr);$i++){
    echo number_format($retArr[$i][0], 4, ".", "").' '.$retArr[$i][1]."\n";
}

function sortResultsByTime($a, $b) {
    return $a[0] > $b[0];
}
?>

Please note the code above yields this for me:请注意上面的代码对我来说是这样的:

0 started (from inside threaded)
0 is ready (from inside threaded)
1 is ready (from inside threaded)
2 is ready (from inside threaded)
3 is ready (from inside threaded)

1609458117.8764 0 started
1609458117.8776 1 is ready
1609458117.8789 2 is ready
1609458117.8802 3 is ready
1609458120.8765 0 is ready

And as expected, the stuff echoed from inside the threads seems weird, however if you store the results, and sort them by the time they were executed, you can see it acts as expected.正如预期的那样,从线程内部回响的东西看起来很奇怪,但是如果您存储结果,并按它们执行的时间对它们进行排序,您可以看到它按预期运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM