草庐IT

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

coder 2024-04-29 原文

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

我正在使用 PCNTL创建一个多线程的 PHP 应用程序。我希望做的是同时运行 3 个函数,我希望它们的返回值合并到一个数组中。所以从逻辑上讲,我需要在所有 child 之间共享一些变量,他们将结果附加到其中,或者仅在单个 child 和父级之间共享三个变量 - 然后父级可以稍后合并结果。

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

此外,如果它有任何效果,那么 fork 进程的函数是一个公共(public)类方法。所以我的代码如下所示:

<?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创建共享内存并将结果保存在那里,还是 children 共享类变量?或者他们只共享全局变量?我相信答案很简单……我只是不知道。

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

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

首先,在您的应用程序中实现多处理时,有两个重要的区别需要理解:
  • 线程 流程 fork 进程
  • 共享内存 消息传递

  • 线程、进程、 fork 进程
  • 线程 :线程的开销非常低,因为它们与父进程在相同的进程空间中运行并共享父进程的内存地址。这意味着为了创建或销毁线程而调用的操作系统更少。如果您打算经常创建和销毁线程,则线程是“便宜”的替代方案。 PHP 没有对线程的 native 支持。但是,从 PHP 7.2 开始,有提供线程功能的 PHP 扩展(用 C 编写)。例如:pthreads
  • 进程 :进程的开销要大得多,因为操作系统必须为它分配内存,而且在像 PHP 这样的解释型语言的情况下,通常有一个完整的运行时必须在您自己的代码执行之前加载和处理。 PHP 确实通过 exec 支持生成进程(同步)或 proc_open (异步)
  • fork 进程 :一个 fork 的过程 split 了这两种方法之间的差异。一个单独的进程在当前进程的内存空间中运行。通过 PCNTL 也有对此的本地支持

  • 为工作选择合适的工具通常是问一个问题:“你多久会启动额外的线程/进程”?如果不是那么频繁(也许您每小时运行一个批处理作业并且该作业可以并行化),那么流程可能是更简单的解决方案。如果进入您的服务器的每个请求都需要某种形式的并行计算,并且您每秒收到 100 个请求,那么线程可能是可行的方法。

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

  • 一个基本的socket解决方案

    首先,我将尝试从 2012 年开始重新创建我的解决方案。@MarcB 将我指向 UNIX sockets .本页明确提及fsockopen ,它打开一个套接字作为文件指针。它还在“另请参阅”部分包含指向 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());
    

    笔记

    此解决方案使用 fork 进程和通过本地(内存中)套接字传递的消息。根据您的用例和设置,这可能不是最佳解决方案。例如:
  • 如果您希望在多个单独的服务器之间拆分处理并将结果传递回中央服务器,则 create_socket_pair不会工作。在这种情况下,您需要创建一个套接字,将套接字绑定(bind)到地址和端口,然后调用 socket_listen等待子服务器的结果。此外,pcntl_fork不能在多服务器环境中工作,因为进程空间不能在不同机器之间共享
  • 如果您正在编写命令行应用程序并且更喜欢使用线程,那么您可以使用 pthreads或抽象 pthread 的第三方库
  • 如果你不喜欢钻研杂草,只想简单的多处理而不必担心实现细节,请查看类似 Amp/Parallel 的库。
  • 最佳答案

    fork 的 child 只要在任何地方写入,就会获得他们自己的内存空间的专用副本 - 这就是“写时复制”。虽然 shmop 确实提供了对公共(public)内存位置的访问,但实际的 PHP 变量和脚本中未定义的内容不会在子级之间共享。

    $x = 7;在一个 child 中的 $x 不会使其他 child 中的 $x 也变成 7。每个 child 都会有自己的专属 $x,完全独立于其他人的副本。

    关于php - 在 PHP 中的子进程之间共享变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8707339/

    有关php - 在 PHP 中的子进程之间共享变量?的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    7. ruby-on-rails - Rails 应用程序之间的通信 - 2

      我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

    8. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

      在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

    9. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    10. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

      刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

    随机推荐