【问题标题】:PHP Threading SimpleXMLElementPHP 线程 SimpleXMLElement
【发布时间】:2014-02-06 02:48:03
【问题描述】:

我已经到处搜索了这个并且我遇到了很多问题,但我不认为这是实际代码的问题。 基本上这段代码在两个单独的线程中启动套接字服务器(登录和游戏),我基本上从非线程版本转换了这段代码,但我一直无法让它为线程工作。

include "socket.php";
include "dep.php";

class Server extends Thread {
    public $server;
    public $config;
    public function __construct($type){
        //$this->config = (string)$type;
        $this->run2($type);
        $this->run();
    }
    public function run(){
        while(true){
            $this->server->loop();
        }
    }
    public function run2($config){
        $this->server = new sokserv($config);
        $this->server->init();
        //while(true){
        //  $this->server->loop();
        //}
    }
}
$login = new Server('Config/config.xml');
$game = new Server("Config/config2.xml");
The error received is 
Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SimpleXMLElement' is not allowed' in C:\Users\=\Desktop\Test\Start.php:19
Stack trace:
#0 C:\Users\=\Desktop\Test\Start.php(19): Server->run2()
#1 C:\Users\=\Desktop\Test\Start.php(10): Server->run2('Config/config.x...')
#2 C:\Users\=\Desktop\Test\Start.php(26): Server->__construct('Config/config.x...')
#3 {main}
  thrown in C:\Users\=\Desktop\Test\Start.php on line 19

但是,恢复到旧代码可以正常工作。 sokserv 的第一部分(我删除了一些公共变量,因为它们包含个人信息)

class sokserv
{
    public $ip;
    public $port;
    public $users = array();
    public $config;
    public $mysql;
    private $socket;
    public function __construct($config = "Config/config.xml")
    {
        $this->readConfig($config);
    }
    public function readConfig($file)
    {
        if (!file_exists($file))
            die("Could not find config");
        $this->config = simplexml_load_file($file);
    }

如果你想要这是 xml:

<server>
    <port>6112</port>
    <ip>0</ip>
    <mysql>
        <host>127.0.0.1</host>
        <username>root</username>
        <dbname></dbname> 
        <password></password>
        <table></table>
    </mysql>
</server>

【问题讨论】:

  • 请分享 sockserv 实现或源代码链接。我认为您可能需要将配置变量转换为数组。
  • 我无法分享 sokserv 类,因为它太大并且包含个人信息,但是我可以分享涉及配置的前几位。
  • 是的,请这样做,您可以只共享一个构造函数
  • 我已经编辑了问题,我还添加了 xml。
  • 对不起,我没有注意到它与 Start.ph 文件有关。请看这个stackoverflow.com/questions/14912551/…

标签: php xml multithreading pthreads simplexml


【解决方案1】:

因为 PHP 本身不支持线程之间共享数据,pthreads 代码正在序列化需要在两个线程之间共享的数据,因此它可以在需要时在另一个线程中创建一个干净的副本(通过反序列化它)。例外情况是简单的标量类型,无论如何都可以轻松复制,而 ThreadWorkerStackable 对象代表正在执行的实际任务,并明确支持同步。

不幸的是,一些 PHP 对象不支持序列化,因为它们在内部实现为指向内存结构的指针,而不仅仅是一组 PHP 变量。 SimpleXML 就是其中的一个示例,因此您会看到错误消息。

有几种方法可以解决这个问题,具体取决于您想变得多“聪明”:

  • 最简单的方法是不在sokserv-&gt;readConfig 方法中解析XML,或者不立即运行该方法。相反,您可以存储文件名或原始 XML 字符串,然后再对其进行解析。然后每个线程将显式创建自己的 SimpleXML 对象,并且只需要在线程之间传递字符串。
  • 相反,您可以完全解析 XML,将所需信息提取到更简单的对象中,然后丢弃 SimpleXMLElement,这一切都在启动线程之前完成。
  • 另一种可能性是给你的sokserv__sleep and __wakeUp methods,或者让它实现the Serializable interface。在这两种情况下,我们的想法是在序列化/睡眠时将$this-&gt;config 转换为 XML 字符串,并在反序列化/唤醒时转换回(新)SimpleXML 对象。
  • 最先进的方法是使用自定义序列化行为包装SimpleXMLElement 本身。请注意,SimpleXMLElement 的简单子类无法重新实现Serializable 接口,如this example 所示。您需要将 SimpleXMLElement 放入某个容器对象或使用委托模式。

无论您选择哪种方法,基本概念都是相同的:线程之间传递的数据(附加到 ThreadWorkerStackable 对象)不能属于 SimpleXML 等不能属于序列化。

【讨论】:

  • @JoeWatkins 我已恢复您的编辑,因为它基本上是一个完全不同的答案。请照此添加,我将撤回我的或将其保留为更一般的解释。
  • 为了教育,最好有一种正确的做事方式。您提到的事情充其量只是解决方法,而信息不正确,是猜测。有一个内置的解决方案,所以我展示了它,我不太关心互联网点,我只是不想混淆已经很难掌握的主题。
  • 如果你要对你应该知道的主题问题给出权威的答案,猜测是不够的。至于其余的:RTFM。
  • @IMSoP Joe Watkins 是 pthreads 的作者,所以我猜他比你更了解它。
  • 我很高兴你学到了一些东西 :) 现在我看到了对我来说很明显的东西,而不是你,我也更新了我的答案。请查看它,为了大家的理智和教育,请删除此答案。
【解决方案2】:

在 TSRM 的帮助下,Zend 内存管理器专门用于禁止上下文共享数据;没有上下文可以在另一个线程中分配或释放任何东西。这被称为无共享架构,它完全按照它在锡上所说的那样做。 pthread 的工作是以安全、理智的方式突破这一障碍。

很明显,与 PHP 捆绑的扩展都没有意识到 pthread,我们也不希望它们知道,因为它们只会变得非常复杂。当涉及到其他扩展提供的对象时,pthreads 假设最安全的做法是序列化对象以进行存储。一些对象禁止这种情况发生,SimpleXML 后代就是这样一组对象。

来自 pthread 的字符串、浮点数、整数和对象不会被序列化。从 pthreads 派生的对象劫持序列化 API,存储对象的物理地址,通过直接访问用户态中表示对象的线程安全结构来避免序列化。

正确的解决方案是将您希望共享的数据包装在从 pthread 派生的对象中:

<?php
class Config extends Stackable {
    /**
    * Constructs thread safe, sharable configuration from complex XML
    * @param mixed $xml         SimpleXMLElement or location of XML file
    * @param array &$objects    reference store
    */
    public function __construct($xml, &$objects) {
        if ($xml instanceof SimpleXMLElement) {
            foreach ($xml as $key => $value)
                $this[$key] = (string) $value;
        } else {
            foreach (simplexml_load_string(
                        $xml) as $key => $value) {
                if ($value->children()) {
                    $this[$key] = new Config($value, $objects);
                } else $this[$key] = (string) $value;
            }
        }

        /* maintain object references */
        $objects[] = $this;
    }

    public function run() {}
}

class Test extends Thread {
    protected $config;

    /**
    * Constructs thread using pre-constructed thread safe, shared configuration object
    * @param Config $config
    */
    public function __construct(Config $config) {
        $this->config = $config;
    }

    public function run() {
        /* iterate over configuration */
        printf("%d settings:\n", count($this->config));
        foreach ($this->config as $key => $data) {
            if (count($data) > 1) {
                printf( 
                    "\t%s, %d settings:\n", $key, count($data));
                foreach ($data as $name => $value) {
                    printf("\t\t%s = %s\n", $name, $value);
                }
            } else printf("\t%s = %s\n", $key, $data);
        }
        printf("\n");

        printf(
            "Host: %s:%d\n", 
            $this->config->ip, 
            $this->config->port);

        printf(
            "MySQL: %s@%s?database=%s&password=%s&table=%s\n", 
            $this->config->mysql->username,
            $this->config->mysql->host,
            $this->config->mysql->dbname,
            $this->config->mysql->password,
            $this->config->mysql->table);
    }
}

/* Example XML */
$xml = <<<XML
<server>
    <port>6112</port>
    <ip>some.ip</ip>
    <mysql>
        <host>127.0.0.1</host>
        <username>root</username>
        <dbname>somedb</dbname> 
        <password>somepass</password>
        <table>sometable</table>
    </mysql>
</server>
XML;

/* Object reference storage */
$objects = [];
$config = new Config($xml, $objects);

$thread = new Test($config);
$thread->start();

$thread->join();
?>

将输出以下内容:

3 settings:
        port = 6112
        ip = some.ip
        mysql, 5 settings:
                host = 127.0.0.1
                username = root
                dbname = somedb
                password = somepass
                table = sometable

Host: some.ip:6112
MySQL: root@127.0.0.1?database=somedb&password=somepass&table=sometable

提供的示例使用您在问题中提供的 [格式] XML,它采用该 XML 并创建它的线程安全表示,它永远不会被序列化。

Config 构造函数中的逻辑完全取决于您使用的 XML 格式。

您可以将该 Config 对象传递给任意数量的线程,所有线程都可以读/写属性,并执行它的方法。

您打算共享的所有数据都应该以这种方式进行管理,您想要从中获得的不是您应该解决异常并尝试存储串行数据,而是您应该为您的数据创建合适的容器,这些容器实际上正确地支持多线程

延伸阅读:https://gist.github.com/krakjoe/6437782

【讨论】:

  • 这个答案对我来说并不完全清楚:你说“一个对象来自 pthreads”,但这个例子实际上来自一个类 Stackable,它显然有一些 ArrayAccess 魔法,因为然后你做$this[$key]。指向完整文档的链接可能很好。也不清楚这是否可用于传输 SimpleXML 对象本身,因为在这里您只是从中提取字符串,这不适用于 OP 提供的 XML,并且可以在没有任何特殊技巧的情况下很好地序列化。
  • 它会序列化,但不是线程安全的,所以不共享。 php.net/intro.pthreads
  • 那么,Stackable 允许 Config 项目可写,但实际上并没有解决 OP 传递 SimpleXML 对象的问题? SimpleXML 序列化问题的实际解决方法似乎是这一行:$this[$key] = (string) $value;
  • 这很棘手:lxr.php.net/xref/PHP_5_4/ext/simplexml/simplexml.c#2636 对象无法以当前形式存储,我提供了正确的数据存储方式以便共享。
  • 如果我错了请纠正我,但似乎让sokserv 扩展Stackable 虽然可能是个好主意,但实际上并不能解决 OP 的问题。 OP 问题的解决方案是将 SimpleXML 对象转换为对序列化安全的形式。您的代码在一个粗略的循环中执行此操作,该循环不适用于问题中的示例 XML,并且没有评论这是解决方案的关键部分。
猜你喜欢
  • 2018-09-01
  • 2020-10-17
  • 1970-01-01
  • 1970-01-01
  • 2013-12-18
  • 2013-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多