在所有 PHP 进程之间共享变量/内存

24

是否可能在所有PHP进程之间共享变量和数组,而不会复制它们

使用memcached,我认为PHP会复制所使用的内存:
$array = $memcache->get('array');
$array 将包含来自memcached的副本。

因此,我的想法是,可以有一个已经定义并在所有进程之间共享的静态变量。


可以再提供一些上下文吗?比如将数据存储在数据库中,在当前脚本中只检索所需的部分。 - Pekka
通常情况下,这可以通过将数据存储在数据库中,并仅获取所需的内容来解决。 - Pekka
@Nuno,请查看http://www.ibm.com/developerworks/library/os-php-shared-memory/。 - Pacerier
5个回答

28

使用 Shmop:

Shmop 是一组易于使用的函数,允许 PHP 读取、写入、创建和删除 Unix 共享内存段。

来源: http://www.php.net/manual/en/intro.shmop.php

构建此扩展不需要外部库。

共享内存函数

  • shmop_close — 关闭共享内存块
  • shmop_delete — 删除共享内存块
  • shmop_open — 创建或打开共享内存块
  • shmop_read — 从共享内存块读取数据
  • shmop_size — 获取共享内存块的大小
  • shmop_write — 将数据写入共享内存块

基本用法

// Create 100 byte shared memory block with system id of 0xff3
$shm_id = shmop_open(0xff3, "c", 0644, 100);
if (!$shm_id) {
    echo "Couldn't create shared memory segment\n";
}

// Get shared memory block's size
$shm_size = shmop_size($shm_id);
echo "SHM Block Size: " . $shm_size . " has been created.\n";

// Lets write a test string into shared memory
$shm_bytes_written = shmop_write($shm_id, "my shared memory block", 0);
if ($shm_bytes_written != strlen("my shared memory block")) {
    echo "Couldn't write the entire length of data\n";
}

// Now lets read the string back
$my_string = shmop_read($shm_id, 0, $shm_size);
if (!$my_string) {
    echo "Couldn't read from shared memory block\n";
}
echo "The data inside shared memory was: " . $my_string . "\n";

//Now lets delete the block and close the shared memory segment
if (!shmop_delete($shm_id)) {
    echo "Couldn't mark shared memory block for deletion.";
}
shmop_close($shm_id);

6
要使用shmop,您需要在配置行中使用**--enable-shmop**参数编译PHP。 - Pang

8

在PHP进程间共享内存的方法之一是安装一个像APC的PHP-bytecode缓存。APC主要用于将字节码存储到操作系统管理的共享内存段中,但它也有一个API可用于在进程之间共享任何你想要的东西(比如本地版本的memcache)。

<?php
   $foobar = array('foo', 'bar');
   apc_store('foobar', $foobar);
?>

然后在其他地方:
<?php
    $foobar = apc_fetch('foobar');
    var_dump($foobar);
?>

共享内存最大的问题在于两个进程很容易互相干扰。因此,共享内存最适合那些变化不太频繁的东西,例如大型全局数组。

那似乎和Memcached一样。不管怎样,谢谢你的回答 :) - Nuno
1
@NunoPeralta,Shmop怎么样?请看下面。 - Pacerier
3
这是错误的,apc_store等不能在进程之间共享内存。每个进程分配自己的内存段。因此,您无法在php-fpm和php-cli之间共享内存(而在不同的php-fpm请求之间共享则可以)。 - bhelm

3

PHP有魔术方法:

  • __get($property) 用于实现对对象 $property 属性的访问
  • __set($property, $value) 用于实现对对象 $property 属性的赋值

PHP可以序列化变量:

  • serialize($variable) 返回变量的字符串表示
  • unserialize($string) 从字符串中返回变量

PHP可以管理文件并处理并发访问:

  • fopen($file, 'c+') 开启一个文件,并启用建议锁选项(允许使用flock)
  • flock($descriptor, LOCK_SH) 获取共享锁(用于读取)
  • flock($descriptor, LOCK_EX) 获取排他锁(用于写入)

因此,在应用程序之间共享对象的最简单方法是创建一个类来实现并使用所有这些东西,以便将其数据即时保存和恢复到一个文件中。

该类的一个简单实现可能是:

class Synchro
{

   private $_file;

   public function __construct($file)
   {
       $this->_file = $file;
   }

   public function __get($property)
   {
       // File does not exist
       if (!is_file($this->_file))
       {
           return null;
       }

       // Check if file is readable
       if ((is_file($this->_file)) && (!is_readable($this->_file)))
       {
           throw new Exception(sprintf("File '%s' is not readable.", $this->_file));
       }

       // Open file with advisory lock option enabled for reading and writting
       if (($fd = fopen($this->_file, 'c+')) === false)
       {
           throw new Exception(sprintf("Can't open '%s' file.", $this->_file));
       }

       // Request a lock for reading (hangs until lock is granted successfully)
       if (flock($fd, LOCK_SH) === false)
       {
           throw new Exception(sprintf("Can't lock '%s' file for reading.", $this->_file));
       }

       // A hand-made file_get_contents
       $contents = '';
       while (($read = fread($fd, 32 * 1024)) !== '')
       {
           $contents .= $read;
       }

       // Release shared lock and close file
       flock($fd, LOCK_UN);
       fclose($fd);

       // Restore shared data object and return requested property
       $object = json_decode($contents);
       if (property_exists($object, $property))
       {
           return $object->{$property};
       }

       return null;
   }

   public function __set($property, $value)
   {
       // Check if directory is writable if file does not exist
       if ((!is_file($this->_file)) && (!is_writable(dirname($this->_file))))
       {
           throw new Exception(sprintf("Directory '%s' does not exist or is not writable.", dirname($this->_file)));
       }

       // Check if file is writable if it exists
       if ((is_file($this->_file)) && (!is_writable($this->_file)))
       {
           throw new Exception(sprintf("File '%s' is not writable.", $this->_file));
       }

       // Open file with advisory lock option enabled for reading and writting
       if (($fd = fopen($this->_file, 'c+')) === false)
       {
           throw new Exception(sprintf("Can't open '%s' file.", $this->_file));
       }

       // Request a lock for writting (hangs until lock is granted successfully)
       if (flock($fd, LOCK_EX) === false)
       {
           throw new Exception(sprintf("Can't lock '%s' file for writing.", $this->_file));
       }

       // A hand-made file_get_contents
       $contents = '';
       while (($read = fread($fd, 32 * 1024)) !== '')
       {
           $contents .= $read;
       }

       // Restore shared data object and set value for desired property
       if (empty($contents))
       {
           $object = new stdClass();
       }
       else
       {
           $object = json_decode($contents);
       }
       $object->{$property} = $value;

       // Go back at the beginning of file
       rewind($fd);

       // Truncate file
       ftruncate($fd, strlen($contents));

       // Save shared data object to the file
       fwrite($fd, json_encode($object));

       // Release exclusive lock and close file
       flock($fd, LOCK_UN);
       fclose($fd);

       return $value;
   }

}

现在,你可以像使用 stdClass 一样使用这个类,但是在构造时需要提供文件路径。
$obj = new Synchro("/tmp/test.sync"); 
$obj->hello = 'world';

// ... and in another process...
echo $obj->hello;

这个例子当然很简单,它只处理对文件的并发访问而不是变量,在更好的实现中,您将使用类似于互斥锁的锁。

我刚刚完成了这个类,并将其推送到了Github上,您可以在这里找到它。


11
你误解了问题。 - Pacerier
1
使用文件可能是最简单和更安全的方式,因为不会尝试访问服务器内存。我认为这比向数据库查询更快。 - Meloman
3
这与使用数据库没有什么不同,其思想是在内存中共享变量,而不是在磁盘上。 - Pablo Pazos
在PHP中,除了使用磁盘文件来共享非预先派生的进程之间的数据外,没有其他方法。APC / APCu和Memcached仅在同一主进程(例如FPM)中工作。如果进程是不同的,则再见朋友,因此这是唯一可能的答案。 - Viktor Joras
我只是想在一个项目中重复使用这个解决方案,但不知道为什么,当我将一个值true放在变量上时,__get函数找不到我的变量。我发现tmp文件末尾有}}而不是}。因此,我添加了fwrite($fd, str_replace('}}','}',json_encode($object)));,但这是一个我不喜欢的解决方法。 - Meloman
这不能用来将某些类型的数据存储为资源。 - Rodrigo Vieira

3
默认情况下是不可能的。每个解决方案都会将内容复制到当前范围内,因为如果不这样做,就无法访问它。
我不知道你想要做什么,但也许你可以在“外部”完成,例如作为gearman作业,然后只捕获进程的结果,而不是整个数组。
您还可以考虑将“大”数组分成片段,然后始终从apc或memcached中检索当前所需的部分。

如果不可能,那么Shmop是什么? - ttvd94
@ttvd94 问题的一部分是“无重复”,这在shmop中也是不可能的。当您调用shmop_read时,返回值实际上是共享内存中实际值的副本。您不能通过引用从shmop读取。 shmop的好处是读取和检索性能,在许多情况下与数据库相比可以忽略不计。 - KingCrunch

0

编辑:
你可能在错误的方式下使用共享内存。
你的共享内存本身就是这样一个数组。因此,你必须直接将单独的多语言字符串存储在共享内存中,而不是将它们存储在大数组中。
然后只需要在特定页面上拉取所需的字符串。
就这样。

一般来说,为了处理某些数据,程序必须通过将其存储在变量中来“复制”它。
这就是变量的作用-存储(或“复制”)一些外部数据。
例如,如果你的数据库中有一些用户信息,在网页上显示用户名,你必须首先将这些数据“复制”,将其存储在PHP变量中。
等等。

你是第一个认为这种方法需要改变的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接