如何正确使用PHP5信号量?

How to properly use PHP5 semaphores?

我有这个函数试图从缓存中读取一些值。但如果值不存在,它应该调用替代源 API 并将新值保存到缓存中。但是,服务器非常过载,几乎每次当值不存在时都会创建一个请求(很多 API 调用)并且每个请求都会将新值存储到缓存中。但是,我想要的是能够多次调用API,但只有一个process/request能够将其存储在缓存中:

function fetch_cache($key, $alternativeSource) {
    $redis = new Redis();
    $redis->pconnect(ENV_REDIS_HOST);
    $value = $redis->get($key);

    if( $value === NULL ) {
        $value = file_get_contents($alternativeSource);

        // here goes part that I need help with
        $semaphore = sem_get(6000, 1); // does this need to be called each time this function is called?
        if( $semaphore === FALSE ) {
            // This means I have failed to create semaphore?
        }

        if( sem_aquire($semaphore, true) ) {
            // we have aquired semaphore so here
            $redis->set($key, $value);
            sem_release($semaphore); // releasing lock
        }

        // This must be call because I have called sem_get()?
        sem_remove($semaphore);
    }

    return $value;
}

在 PHP5 中是否正确使用了信号量?

简答

  1. 您不需要在 fetch_cache 函数中创建和删除信号量。将 sem_get() 放入初始化方法(例如 __construct)。
  2. 您应该使用 sem_remove() 删除信号量,但要使用清理方法(例如 __destruct)。或者,您可能希望将它们保留更长时间 - 取决于您的应用程序的逻辑。
  3. 使用sem_acquire()获取锁,sem_release()释放锁。

描述

sem_get()

创建一组 three 信号量。

底层 C 函数 semget 非原子函数 。当两个进程试图调用 semget 时,有可能 race condition。因此,在某些初始化过程中应该调用semget。 PHP 扩展通过三个信号量解决了这个问题:

信号量 0 a.k.a。 SYSVSEM_SEM

初始化为 sem_get$max_acquire 并随着进程获取它而递减。

调用 sem_get 的第一个进程获取 SYSVSEM_USAGE 信号量的值(见下文)。对于第一个进程,它等于 1,因为 semget 之后具有原子 semop 函数的扩展 sets it to 1。如果这真的是第一个进程,扩展程序会将 SYSVSEM_SEM 信号量值分配给 $max_acquire.

信号量 1 a.k.a。 SYSVSEM_USAGE

使用信号量的进程数。

信号量 2 a.k.a。 SYSVSEM_SETVAL

对内部 SETVALGETVAL 操作起到锁的作用(参见 man 2 semctl)。例如,它设置为 1,而扩展设置 SYSVSEM_SEM$max_acquire,然后重置为零。

最后,sem_get 将一个结构(包含信号集 ID、密钥和其他信息)包装到 PHP 资源中并 returns 它。

所以你应该在一些初始化过程中调用它,当你只准备使用信号量时。

sem_acquire()

这里是 $max_acquire goes into play.

SYSVSEM_SEM 的值(我们称之为 semval)最初等于 $max_acquiresemop() 阻塞,直到 semval 变得大于或等于 1。然后semval减去1.

如果$max_acquire = 1,则semval在第一次调用后变为零,接下来对sem_acquire()的调用将阻塞,直到semval被[=16=恢复] 打电话。

当您需要从可用集合 ($max_acquire) 中获取下一个“锁”时调用它。

sem_release()

sem_acquire() 几乎相同,只是它增加了 SYSVSEM_SEM 的值。

当您不再需要之前使用 sem_acquire() 获得的“锁”时调用它。

sem_remove()

立即删除信号量集,唤醒在该集 semop 中阻塞的所有进程(来自 IPC_RMID 部分,SEMCTL(2) 手册页)。

所以这实际上与使用 ipcrm 命令删除信号量相同。

文件权限应该是 0666,而不是 6000,因为您正在尝试执行此操作。