澄清一下上面提到的“进程”的含义
在 Apache Web 服务器上,许多 PHP 请求将在同一个进程空间内执行,因为它支持多线程。但是,任何由脚本获取和获取的信号量,如果未释放和删除,仍然会在脚本每次终止时由 PHP 解释器自动清理。
在发送电子邮件之前删除任何垃圾!
(PHP 4, PHP 5, PHP 7, PHP 8)
sem_acquire — 获取信号量
sem_acquire() 默认情况下会阻塞(如果需要)直到可以获取信号量。尝试获取其已获取的信号量的进程,如果获取信号量会导致其信号量数量超过最大值,则将无限期阻塞。
处理请求后,进程获取但未明确释放的任何信号量将被自动释放,并将生成警告。
澄清一下上面提到的“进程”的含义
在 Apache Web 服务器上,许多 PHP 请求将在同一个进程空间内执行,因为它支持多线程。但是,任何由脚本获取和获取的信号量,如果未释放和删除,仍然会在脚本每次终止时由 PHP 解释器自动清理。
在发送电子邮件之前删除任何垃圾!
sem_acquire() 是阻塞的,这意味着对同一信号量的后续调用将无限期阻塞,直到信号量被释放。这确保了序列化,但如果你只想检查是否应该继续执行,则它不太实用。不幸的是,PHP 尚未支持任何以非阻塞方式查询信号量状态的方法。
使用共享内存(shm_ 函数)可以手动构建这样的机制。但是,要注意它并不简单,最终没有效果。例如,你不能简单地选择一个共享内存变量,存储信号量键并查询它。这种操作将是非事务性的且非原子的,即两个或多个并行进程有可能在其中一个进程成功将其标记为“已锁定”之前从共享内存变量中读取“未锁定”。你必须使用(阻塞)信号量来序列化对共享内存变量的访问,从而重新创建你试图解决的问题。
换句话说,如果非阻塞查询对你至关重要,你必须请求 PHP 设计人员解决这个问题,或者选择另一种执行锁定的机制,一种已经具有此功能的机制。
注意,当你重置 $sem_identifier 时,信号量将不再阻塞!
这段代码不工作
$key = ftok(__FILE__,'m');
$a = sem_get($key);
sem_acquire($a);
$a = false;
而这段代码工作
$key = ftok(__FILE__,'m');
$a = sem_get($key);
sem_acquire($a);
//$a = false;
因此:为你的标识符使用唯一的变量名称!
我最近必须使用信号量来避免并发运行数据库更新。
为了测试它,我在同一个桌面(Chrome)上使用了两个浏览器窗口。
在这种情况下,信号量没有按预期工作。
同样,对文件的 flock 也没有按预期工作。
从命令行运行脚本按预期工作。
一旦我使用了两个不同的浏览器,一个在我的桌面上,另一个在虚拟机中,它按预期工作。
希望这能帮助任何人避免在该主题上进行奇怪的调试会话...
如果你需要非阻塞信号量,以下是如何实现它的示例。使用共享内存变量来标记是否存在锁,然后使用信号量围绕对该变量的操作。我将我的共享变量称为“token”。
<?php
echo '<pre>';
$resourceSemaphore = sem_get(7);
$tokenSemaphore = sem_get(8);
$tokenValue = shm_attach(9, 100);
function myEcho($v) {
echo microtime() . ' ' . $v . "\n";
}
// sem_remove($resourceSemaphore);
// sem_remove($tokenSemaphore);
// exit();
function try_lock() {
global $resourceSemaphore, $tokenSemaphore, $tokenValue;
myEcho('begin try_lock()');
myEcho('acquire token semaphore');
sem_acquire($tokenSemaphore);
myEcho(' token semaphore acquired');
$tmp = shm_get_var($tokenValue, 6);
myEcho(' token value: ' . var_export($tmp, true));
$exit = $tmp;
if (!$exit) {
$tmp = shm_put_var($tokenValue, 6, true);
$tmp = shm_get_var($tokenValue, 6);
myEcho(' token new value: ' . var_export($tmp, true));
}
myEcho('release token semaphore');
sem_release($tokenSemaphore);
if ($exit) return false;
myEcho('acquire resource semaphore');
sem_acquire($resourceSemaphore);
myEcho(' resource semaphore acquired');
return true;
}
function release() {
global $resourceSemaphore, $tokenSemaphore, $tokenValue;
myEcho('release resource semaphore');
sem_release($resourceSemaphore);
myEcho('acquire token semaphore');
sem_acquire($tokenSemaphore);
myEcho(' token semaphore acquired');
$tmp = shm_get_var($tokenValue, 6);
myEcho(' token value: ' . var_export($tmp, true));
$tmp = shm_put_var($tokenValue, 6, false);
$tmp = shm_get_var($tokenValue, 6);
myEcho(' token new value: ' . var_export($tmp, true));
myEcho('release token semaphore');
sem_release($tokenSemaphore);
}
for ($triesLeft = 5; $triesLeft > 0 && !try_lock(); $triesLeft--) {
myEcho('failed to acquire resource');
myEcho('wait for 1 sec');
sleep(1);
myEcho('try again');
}
myEcho(' access the resource for 4 sec');
//paste here your code, accessing your resource
sleep(4);
release();
myEcho('the end');
?>
当我在两个并行实例中执行此脚本时,我得到以下输出
-------(第一个实例)----------------------------------------
... 482 begin try_lock()
... 482 acquire token semaphore
... 482 token semaphore acquired
... 482 token value: false
... 482 token new value: true
... 482 release token semaphore
... 482 acquire resource semaphore
... 482 resource semaphore acquired
... 482 access the resource for 4 sec
... 486 release resource semaphore
... 486 acquire token semaphore
... 486 token semaphore acquired
... 486 token value: true
... 486 token new value: false
... 486 release token semaphore
... 486 the end
-------(第二个实例)----------------------------------------
... 485 begin try_lock()
... 485 acquire token semaphore
... 485 token semaphore acquired
... 485 token value: true
... 485 release token semaphore
... 485 failed to acquire resource
... 485 wait for 1 sec
...
... 486 wait for 1 sec
... 487 try again
... 487 begin try_lock()
... 487 acquire token semaphore
... 487 token semaphore acquired
... 487 token value: false
... 487 token new value: true
... 487 release token semaphore
... 487 acquire resource semaphore
... 487 resource semaphore acquired
... 487 access the resource for 4 sec
... 491 release resource semaphore
... 491 acquire token semaphore
... 491 token semaphore acquired
... 491 token value: true
... 491 token new value: false
... 491 release token semaphore
... 491 the end
不幸的是,PHP 目前不支持非阻塞信号量。
如果需要类似的功能,可以将信号量与共享内存结合使用,以创建自己的非阻塞锁定机制。
使用共享内存变量来标记是否已存在锁,然后使用信号量对该变量的操作进行保护。