在PHP中的子进程之间共享变量?

时间:2012-01-03 02:33:29

标签: php multithreading shared-memory

我确定我所尝试的内容非常简单,但我之前从未完全使用过多线程,所以我不知道从哪里开始。

我使用PCNTL创建多线程PHP应用程序。我想要做的是同时运行3个函数,我希望它们的返回值合并为一个数组。所以逻辑上我需要在他们追加结果的所有子项中共享一些变量,或者只在一个子项和父项之间共享三个变量 - 然后父项可以在以后合并结果。

问题是 - 我不知道该怎么做。首先想到的是使用shared memory,但我觉得应该有一个更简单的方法。

此外,如果它有任何影响,分叉进程的函数是一个公共类方法。所以我的代码如下所示:

<?php
    class multithreaded_search {
        /* ... */
        /* Constructors and such */
        /* ... */
        public function search( $string = '' ) {
            $search_types = array( 'tag', 'substring', 'levenshtein' );
            $pids = array();
            foreach( $search_types as $type ) {
                $pid = pcntl_fork();
                $pids[$pid] = $type;
                if( $pid == 0 ) { // child process
                    /* confusion */
                    $results = call_user_func( 'multithreaded_search::'.$type.'_search', $string );
                    /* What do we do with $results ? */
                }
            }
            for( $i = 0; $i < count( $pids ); $i++ ) {
                $pid = pcntl_wait();
                /* $pids[$pid] tells me the type of search that just finished */
                /* If we need to merge results in the parent, we can do it here */
            }
            /* Now all children have exited, so the search is complete */
            return $results;
        }
        private function tag_search( $string ) {
            /* perform one type of search */
            return $results;
        }
        private function substring_search( $string ) {
            /* perform one type of search */
            return $results;
        }
        private function levenshtein_search( $string ) {
            /* perform one type of search */
            return $results;
        }
    }
?>

因此,在调用shmop_open创建共享内存并将结果保存到那里之前,我需要使用pcntl_fork,还是让子共享类变量?或者他们只共享全局变量?我确定答案很简单......我只是不知道。

答案(对于任何发现此问题的人)

我已经有了几年的经验,所以我会尝试传授一些知识。

首先,在应用程序中实现多处理时,有两个重要的区别:

  • 主题 流程 分叉流程
  • 共享内存 消息传递

线程,进程,分叉进程

  • 线程 :线程的开销非常低,因为它们在与父级相同的进程空间中运行并共享父级的内存地址。这意味着更少的OS调用以创建或销毁线程。线程是廉价的&#34;如果您打算经常创建和销毁它们,请选择其他方法。 PHP没有对线程的本机支持。但是从PHP 7.2开始,有PHP扩展(用C编写)提供了线程功能。例如:pthreads
  • 进程 :进程的开销要大得多,因为操作系统必须为它分配内存,对于像PHP这样的解释语言,会有通常是在您自己的代码执行之前必须加载和处理的整个运行时。 PHP确实通过exec(同步)或proc_open(异步)生成对产生进程的本机支持
  • 分叉流程 :分叉流程会分割这两种方法之间的差异。在当前进程的内存空间中运行单独的进程。通过pctnl
  • 还可以对此进行原生支持

为工作选择合适的工具通常需要提出一个问题:&#34;你多久会开出更多的线程/流程&#34;?如果不经常这样(也许你每小时运行一次批处理作业并且可以并行化作业)那么进程可能是更容易的解决方案。如果进入服务器的每个请求都需要某种形式的并行计算,并且每秒会收到100个请求,那么线程可能就好了。

共享内存,消息传递

  • 共享内存 :这是允许多个线程或进程写入RAM的同一部分的时间。这样做的好处是非常快速且易于理解 - 它就像办公空间中的共享白板。任何人都可以阅读或写信。但是,在管理并发性方面存在一些缺点。想象一下,如果两个进程在同一时间写入内存中的完全相同的位置,那么第三个进程会尝试读取结果。它会看到哪个结果? PHP通过shmop对共享内存提供本机支持,但要正确使用它需要锁,信号量,监视器或其他复杂的系统工程过程
  • 消息传递 :这是自70年代以来一直存在的热门新事物#34;™。我的想法是,不是写入共享内存,而是写入自己的内存空间,然后告诉其他线程/进程&#34;嘿,我有一条消息给你&#34;。 Go编程语言有一个与此相关的着名座右铭:&#34;不要通过共享内存进行通信,通过沟通来共享内存。传递消息的方法有很多种,包括:写入文件,写入套接字,写入stdout,写入共享内存等等。

基本套接字解决方案

首先,我将尝试从2012年开始重新构建我的解决方案。@MarcB将我指向UNIX sockets。该页面明确提到fsockopen,它打开一个套接字作为文件指针。它还包括在&#34;参见&#34;部分指向socket_connect的链接,它可以让您对套接字进行更低级别的控制。

当时我可能花了很长时间研究这些socket_*函数,直到我得到了一些工作。现在,我对socket_create_pair进行了快速谷歌搜索,找到了this helpful link to get you started

我重写了上面的代码,将结果写入UNIX套接字,并将结果读入父线程:

<?php
/*
 * I retained the same public API as my original StackOverflow question,
 * but instead of performing actual searches I simply return static data
 */

class multithreaded_search {
    private $a, $b, $c;
    public function __construct($a, $b, $c) {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    public function search( $string = '' ) {
        $search_types = array( 'tag', 'substring', 'levenshtein' );
        $pids = array();
        $threads = array();
        $sockets = array();
        foreach( $search_types as $type ) {
            /* Create a socket to write to later */
            $sockets[$type] = array();
            socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets[$type]);
            $pid = pcntl_fork();
            $pids[] = $pid;
            $threads[$pid] = $type;
            if( $pid == 0 ) { // child process
                /* no more confusion */
                $results = call_user_func( 'multithreaded_search::'.$type.'_search', $string );
                /* What do we do with $results ? Write them to a socket! */
                $data = serialize($results);
                socket_write($sockets[$type][0], str_pad($data, 1024), 1024);
                socket_close($sockets[$type][0]);
                exit();
            }
        }
        $results = [];
        for( $i = 0; $i < count( $pids ); $i++ ) {
            $pid = $pids[$i];
            $type = $threads[$pid];
            pcntl_waitpid($pid, $status);
            /* $threads[$pid] tells me the type of search that just finished */
            /* If we need to merge results in the parent, we can do it here */
            $one_result = unserialize(trim(socket_read($sockets[$type][1], 1024)));
            $results[] = $one_result;
            socket_close($sockets[$type][1]);
        }
        /* Now all children have exited, so the search is complete */
        return $results;
    }

    private function tag_search() {
        return $this->a;
    }

    private function substring_search() {
        return $this->b;
    }

    private function levenshtein_search() {
        return $this->c;
    }
}

$instance = new multithreaded_search(3, 5, 7);
var_dump($instance->search());

注释

此解决方案使用分叉进程和通过本地(内存中)套接字传递消息。根据您的使用案例和设置,这可能不是最佳解决方案。例如:

  • 如果您希望在多个单独的服务器之间拆分处理并将结果传回中央服务器,则create_socket_pair无法正常工作。在这种情况下,您需要创建套接字,将套接字绑定到地址和端口,然后调用socket_listen以等待子服务器的结果。此外,pcntl_fork无法在多服务器环境中工作,因为不能在不同的计算机之间共享进程空间
  • 如果您正在编写命令行应用程序并且更喜欢使用线程,那么您可以使用pthreads或抽象pthreads的第三方库
  • 如果您不喜欢挖掘杂草,只想简单的多处理而不必担心实施细节,请查看像Amp/Parallel这样的库

3 个答案:

答案 0 :(得分:5)

分叉的孩子一旦写到任何地方就会获得他们自己专用的内存空间 - 这就是“写时复制”。虽然shmop确实提供了对公共内存位置的访问,但是不会在子代之间共享实际的PHP变量和脚本中定义的内容。

在一个孩子中做$x = 7;不会使其他孩子的$ x也变为7.每个孩子都有自己的专用$ x,完全独立于其他人的副本。

答案 1 :(得分:1)

只要父子知道共享内存段的键/键可以在pcnlt_fork之前执行shmop_open。但请记住,pcnlt_fork在子进程中返回0,而在创建子进程失败时返回-1(检查注释附近的代码/ confusion /)。父亲将在$ pid中创建刚刚创建的子进程的PID。

在此处查看:

http://php.net/manual/es/function.pcntl-fork.php

答案 2 :(得分:0)

使用此课程: http://pastebin.com/0wnxh4gY

http://framework.zend.com/manual/1.7/en/zendx.console.process.unix.overview.html

利用shm函数通过setVariable方法在多个进程之间共享变量......显然你应该使用它在某种cgi模式下运行PHP很可能是php-fpm