我确定我正在尝试的是非常简单的,但我以前从未完全使用多线程,所以我不知道从哪里开始.
我正在使用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;
}
}
?>
因此,在调用pcntl_fork创建共享内存并将结果保存在那里之前,我需要使用shmop_open,还是让子共享类变量?或者他们只共享全局变量?我确定答案很简单……我只是不知道.
答案(对于任何发现此问题的人)
我有几年的经验,所以我会尝试传授一些知识.
首先,在应用程序中实现多处理时,有两个重要的区别:
>线程与进程与分叉进程
>共享内存与消息传递
线程,进程,分叉进程
>线程:线程的开销非常低,因为它们在与父项相同的进程空间中运行并共享父项的内存地址.这意味着更少的OS调用以创建或销毁线程.如果您计划经常创建和销毁它们,则线程是“便宜”的替代方案. PHP没有对线程的本机支持.但是从PHP 7.2开始,有PHP扩展(用C编写)提供了线程功能.例如:pthreads
>进程:进程的开销要大得多,因为操作系统必须为它分配内存,对于像PHP这样的解释语言,通常需要在自己的代码执行之前加载和处理整个运行时. PHP确实通过exec(同步)或proc_open(异步)对产生进程进行本机支持
>分叉流程:分叉流程将这两种方法之间的差异分开.在当前进程的内存空间中运行单独的进程.通过pctnl也有本地支持
为工作选择合适的工具通常需要提出一个问题:“你多久会开出更多的线程/流程”?如果不经常这样(也许你每小时运行一次批处理作业并且可以并行化作业)那么进程可能是更容易的解决方案.如果进入服务器的每个请求都需要某种形式的并行计算,并且每秒收到100个请求,那么线程可能就是这样.
共享内存,消息传递
>共享内存:这是允许多个线程或进程写入RAM的同一部分的时间.这样做的好处是非常快速且易于理解 – 它就像是办公空间中的共享白板.任何人都可以阅读或写信.但是,在管理并发性方面存在一些缺点.想象一下,如果两个进程在同一时间写入内存中的完全相同的位置,那么第三个进程会尝试读取结果.它会看到哪个结果? PHP通过shmop对共享内存提供原生支持,但正确使用它需要锁,信号量,监视器或其他复杂的系统工程进程
>消息传递:这是自70年代以来一直存在的“热门新事物”.我的想法是,不是写入共享内存,而是写入自己的内存空间,然后告诉其他线程/进程“嘿,我有一条消息给你”. Go编程语言有一个与此相关的着名座右铭:“不要通过共享内存进行通信,通过通信共享内存”.传递消息的方法有很多种,包括:写入文件,写入套接字,写入stdout,写入共享内存等.
首先,我将尝试从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());
笔记
此解决方案使用分叉进程和通过本地(内存)套接字传递消息.根据您的使用案例和设置,这可能不是最佳解决方案.例如:
>如果您希望在几个单独的服务器之间拆分处理并将结果传递回中央服务器,则create_socket_pair将不起作用.在这种情况下,您需要创建套接字,将套接字绑定到地址和端口,然后调用socket_listen以等待子服务器的结果.此外,pcntl_fork在多服务器环境中不起作用,因为不能在不同的机器之间共享进程空间
>如果您正在编写命令行应用程序并且更喜欢使用线程,那么您可以使用pthreads或抽象pthreads的第三方库
>如果您不喜欢挖掘杂草并且只是想要简单的多处理而不必担心实现细节,请查看像Amp/Parallel这样的库
解决方法:
分叉的孩子一旦写到任何地方就会获得他们自己专用的内存空间 – 这就是“写时复制”.虽然shmop确实提供了对公共内存位置的访问,但是在子代之间不共享实际的PHP变量和脚本中定义的内容.
做$x = 7;在一个孩子中,不会让其他孩子的$x也变成7.每个孩子都有自己的专用$x,完全独立于其他人的副本.