我的并发编程逻辑出了什么问题?

时间:2013-06-05 22:53:29

标签: php multithreading concurrency

我同时向蜘蛛网页写了一个网络蜘蛛。对于蜘蛛找到的每个链接,我想要分叉一个重新开始该过程的新子节点。

我不想重载目标服务器,所以我创建了一个所有对象都可以访问的静态数组。每个孩子都可以将他们的PID添加到数组中,父母或孩子应检查数组以查看是否已满足$ maxChildren,如果满足,则耐心等待任何孩子完成。

如您所见,我将$ maxChildren设置为3.我期望在任何给定时间看到3个同步进程。但事实并非如此。 linux top命令在任何给定时间显示12到30个进程。在并发编程中,如何调节同时进程的数量?我的逻辑目前受到Apache如何处理它的最大孩子的启发,但我不确定它是如何工作的。

正如其中一个答案中所指出的,全局访问静态变量会带来竞争条件的问题。为了解决这个问题,$ children数组将进程的唯一$ PID作为键和值,从而创建一个唯一值。我的想法是,因为任何对象只能处理一个$ children [$ pid]值,所以不需要锁定。这不是真的吗?是否有可能两个进程在某个时候尝试取消设置或添加相同的值?

private static $children = array();

private $maxChildren = 3;

public function concurrentSpider($url) {

        // STEP 1:
        // Download the $url
        $pageData = http_get($url, $ref = '');

        if (!$this->checkIfSaved($url)) {
            $this->save_link_to_db($url, $pageData);
        }

        // STEP 2:
        // extract all hyperlinks from this url's page data
        $linksOnThisPage = $this->harvest_links($url, $pageData);

        // STEP 3:
        // Check the links array from STEP 2 to see if this page has
        // already been saved or is excluded because of any other
        // logic from the excluded_link() function
        $filteredLinks = $this->filterLinks($linksOnThisPage);

        shuffle($filteredLinks);

        // STEP 4: loop through each of the links and
        // repeat the process
        foreach ($filteredLinks as $filteredLink) {

            $pid = pcntl_fork();
            switch ($pid) {
                case -1:
                    print "Could not fork!\n";
                    exit(1);
                case 0:
                    if ($this->checkIfSaved($filteredLink)) {
                        exit();
                    }
                    //$pid = getmypid();
                    print "In child with PID: " . getmypid() . " processing $filteredLink \n";


                    $var[$pid]->concurrentSpider($filteredLink);
                    sleep(2);

                    exit(1);
                default:
                    // Add an element to the children array
                    self::$children[$pid] = $pid;
                    // If the maximum number of children has been
                    // achieved, wait until one or more return
                    // before continuing.

                    while (count(self::$children) >= $this->maxChildren) {
                        //print count(self::$children) . " children \n";
                        $pid = pcntl_waitpid(-1, $status);
                        unset(self::$children[$pid]);
                    }
            }
        }
    }

这是用PHP编写的。我知道参数为-1的pcntl_waitpid函数等待任何子项完成而不管父项(http://php.net/manual/en/function.pcntl-waitpid.php)。

我的逻辑出了什么问题?如何更正它以便只有$maxChildren进程同时运行?如果您有建议,我也愿意改进逻辑。

4 个答案:

答案 0 :(得分:4)

首先要注意的是:如果这是多个线程之间真正的全局共享,那么多个线程可能会立即添加到它并且您正在遇到竞争条件。您需要某种并发控制来确保只有一个进程一次访问您的全局数组。

此外,尝试简单的调试技巧,让每个进程在每次分叉新蜘蛛时写出(到控制台或文件)其PID和全局数组的全部内容。它将帮助您检查您的假设(在某些时候显然是错误的)并找出问题所在。

编辑:(回应评论)

我不是PHP开发人员,但如果我不得不猜测,基于您使用的操作系统工具来计算操作系统级别的进程,我猜你的fork会产生多个进程,但是您的static数组在当前流程中是全局 。实现系统范围的共享内存要复杂得多!

如果您只想计算某些内容并确保共享资源的实例不会失控,请查看semaphores,看看您是否可以在PHP中找到一种方法来创建命名信号量对象可以在蜘蛛的多个实例之间共享。

答案 1 :(得分:1)

使用真正的编程语言;)

第1步有点不好,如果它可能在数据库中,你为什么要下载?把它放在if里面,看看你是否可以在它周围放一个互斥锁。也许在sql中可以模仿一个。

我希望harvest_links使用适当的html处理器和css选择器支持(我喜欢fizzler for .NET)。我想正则表达式如果只是为了得到链接就可以了,但它可能会搞砸。

我看到第4步,我不认为它很糟糕但个人我会以不同的方式做到这一点。 我会像第一步那样将url,page,flag插入db。然后我会有另一个进程或同一个进程向db请求未处理的页面,如果错误则将标志设置为某个值,如果成功则将其设置为另一个值。如果进程退出(关闭,崩溃,断电等)失败,它就可以轻松地将其拾取并且不需要扫描每个页面以找到它停止的位置。它只是向数据库询问下一个链接并重做它没有完成的内容

答案 2 :(得分:0)

PHP不支持多线程,因此它不支持互斥锁或任何其他同步方法。正如其他人在答案中所说,这会导致竞争状况。

你必须用C或bash编写一个包装器。这样,PHP脚本可以将目标提交给包装器,包装器将处理调度。

另一种方法是用Python或Ruby重写你的蜘蛛,两者都支持多线程。这将消除进程间通信的需要。

编辑:第二个想法,最好的方法是用Python或Ruby编写包装器,并将现有的PHP代码重用为黑盒子。这是对上述解决方案的妥协。

答案 3 :(得分:0)

如果蜘蛛是出于实际目的,你可能想谷歌“卷曲多线程”

cURL Multi Threading with PHP