2

我同时为蜘蛛页面编写了一个网络蜘蛛。对于蜘蛛找到的每个链接,我想派生一个新的孩子重新开始这个过程。

我不想让目标服务器超载,所以我创建了一个所有对象都可以访问的静态数组。每个孩子都可以将他们的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 编写的。我知道pcntl_waitpid参数为 -1 的函数等待任何孩子完成,而不管父母(http://php.net/manual/en/function.pcntl-waitpid.php)。

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

4

4 回答 4

4

首先要注意:如果这确实是多个线程之间共享的全局,则有可能多个线程同时添加到它,并且您正在与竞争条件发生冲突。您需要某种并发控制来确保只有一个进程同时访问您的全局数组。

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

编辑:(回应评论)

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

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

于 2013-06-05T23:02:15.073 回答
1

使用真正的编程语言;)

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

我希望 Harvest_links 使用具有 css 选择器支持的适当 html 处理器(我喜欢 .NET 的 fizzler)。我想如果只是为了获取链接,正则表达式会很好,但可能会搞砸。

我看到了第 4 步,我认为这并不坏,但我个人会以不同的方式来做。我会像第一步一样将 url、page、flag 插入到数据库中。然后我会有另一个进程或同一个进程向数据库询问未处理的页面,如果出错则将标志设置为某个值,如果成功则将标志设置为另一个值。因此,如果进程退出(关闭、崩溃、断电等)出现故障,它可以轻松获取它,而无需扫描每一页以找到它停止的位置。它只是向数据库询问下一个链接并重做它没有完成的事情

于 2013-06-05T23:24:35.407 回答
0

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

使用 PHP 的 cURL 多线程

于 2013-06-22T12:35:09.883 回答
0

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

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

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

编辑:再三考虑,最好的方法是用 Python 或 Ruby 编写包装器,并将现有的 PHP 代码作为黑盒重用。这是上述解决方案的折衷方案。

于 2013-06-06T04:24:12.120 回答