在RAII类似的PHP类中实现共享内存和信号量的正确策略

4

什么情况下会出现这种情况?

如果您正在使用共享内存和信号量进行进程间锁定(使用 pcntl 扩展),则应关注信号量和共享内存段的生命周期。例如,您正在编写后台工作程序,并使用主进程和一些子进程(已分叉)来处理作业。在它们之间使用共享内存和信号量是个好主意。而像 shm_xxx 和 sem_xxx php 函数周围的 RAII 类包装器看起来也是个好主意。

示例

class Semaphore
{
     private $file;

     private $sem;

     public function __construct()
     {
        $this->file = tempnam(sys_get_temp_dir(), 's');
        $semKey = ftok($this->file, 'a');

        $this->sem = sem_get($semKey, 1); //auto_release = 1 by default
     }

     public function __destruct()
     {
         if (is_resource($this->sem) {
            sem_remove($this->sem);
         }
     }

     ....
}

不是好的选择-在分叉之后,父进程和子进程各自拥有一个实例。如果其中任何一个实例的析构函数销毁信号量,则会导致问题。

为什么重要

大多数Linux系统的信号量共享内存数量都有限制。 如果您有应用程序需要创建和删除许多信号量的共享内存段,则无法等待它在进程关闭时自动释放。

问题

使用c,您可以使用带有IPC_RMIDshmctl - 它将标记该段以供删除。实际的删除发生在当前连接到该段的最后一个进程正确分离它时。当然,如果当前没有进程连接到该段,则似乎立即删除。它像简单的引用计数器一样工作。但是,PHP并未实现shmctl。

另一种策略是仅在主进程的析构函数中销毁信号量:

class Semaphore
{
     ... 
     private $pid;

     public function __construct()
     {
        $this->pid = getmypid();
        ...
     }

     public function __destruct()
     {
         if (is_resource($this->sem) && $this->pid === getmypid()) {
            sem_remove($this->sem);
         }
     }
     ....
}

问题是:

  1. 是否有任何方法在 PHP 中使用 IPC_RMID?
  2. 在这种情况下应该使用什么策略?只在主进程中销毁?还是其他情况需要考虑?
1个回答

1
我检查了当前的PHP源代码,并且没有使用IPC_RMID。然而,PHP使用semop()和它,SEM_UNDO标志,以防auto_release(参见PHP sem_get()手册)被设置。但请注意,这仅适用于每个进程级别。因此,在您将PHP用作Apache模块,FCGI或FPM时,它可能无法按预期工作。不过,对于CLI应该可以很好地工作。
对于您的清理工作,它取决于“主”是否最后终止。
如果您不知道,可以自己实现引用计数。
class Semaphore
{
    static private $m_referenceCount = 0;

    public function __construct()
    {
        ++self::$m_referenceCount;
        // aquire semaphore
    }

    public function __destruct()
    {
        if (--self::$m_referenceCount <= 0) {
            // clean up
        }
    }
}

但是请注意,在某些情况下析构函数不会被执行


是的,对于CLI情况而言,我的问题比其他实际情况更为重要。自制的引用计数有一个关键问题 - 你必须确保引用增加/减少是原子操作。因此,你应该有一些进程间锁来实现这一点。是吗? - Nick Bondarenko
对于原生大小的整数,递增或递减操作肯定是原子性的!不确定是否原子性的是递减和比较操作。但即使这不是原子性的,它在某些情况下仍然可以可靠地工作:如果您首先创建所有实例,然后在任何时间一个接一个地删除它们,而不创建新实例,则会清理干净。可能会清理两次或更多次,但至少一次。 - Shi
是的,我的意思是应该在一些独立的CLI进程中进行原子互锁 - 在简单情况下有一个主进程和一个从进程。PHP的共享内存实现没有任何原子操作。因此,您应该自己进行锁定。该锁定的基本实现是共享内存+信号量。但是如何确保锁定对象的生命周期呢? - Nick Bondarenko

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