apcu_cas 的定义如下:
bool apcu_cas(string $key, int|float $old, int|float $new)
这个函数的作用是:如果键 $key 的当前值等于 $old,则将其值设置为 $new,并返回 true;否则返回 false。
值得注意的是,它只能用于数值型(int 或 float)的值,否则操作将失败。
apcu_store('counter', '5');
$result = apcu_cas('counter', 5, 10);
问题说明:
虽然 '5' 看起来像是数字,但在 apcu_cas 内部,类型检查是严格的。字符串 '5' 与整数 5 是不相等的,因此 cas 操作会失败。
解决方法:
确保存储时就使用数值类型:
apcu_store('counter', 5);
或在比较前强制转换:
$value = apcu_fetch('counter');
if (is_numeric($value)) {
apcu_store('counter', (int)$value);
}
apcu_store('rate', 1.0);
apcu_cas('rate', 1, 2.0);
问题说明:
尽管 1 和 1.0 在数学意义上相等,但 apcu_cas 在比较时会将类型也考虑在内,int 和 float 不一致导致比较失败。
解决方法:
使用一致的数值类型,推荐根据原始值的类型来传入参数:
$value = apcu_fetch('rate');
if (is_float($value)) {
apcu_cas('rate', 1.0, 2.0);
}
apcu_delete('score');
$result = apcu_cas('score', 0, 1);
问题说明:
如果键不存在,apcu_cas 将直接返回 false,不会创建新的键值。这可能会导致开发者误以为操作失败是由于值不同,而非键不存在。
解决方法:
在调用 apcu_cas 前先用 apcu_exists 或 apcu_fetch 确认键是否存在:
if (apcu_exists('score')) {
apcu_cas('score', 0, 1);
} else {
apcu_store('score', 1);
}
始终使用 int 或 float 存储数值,避免使用字符串表示的数字。可以封装一层数据写入逻辑:
function set_numeric_apcu(string $key, int|float $value): void {
apcu_store($key, $value);
}
创建一个安全包装函数,确保类型一致再执行 apcu_cas:
function safe_apcu_cas(string $key, int|float $old, int|float $new): bool {
$current = apcu_fetch($key);
if (gettype($current) !== gettype($old)) {
return false;
}
return apcu_cas($key, $old, $new);
}
在并发环境下,初始化值的流程应清晰明确,可以通过如下方式保证键存在且类型正确:
$key = 'counter';
if (!apcu_exists($key)) {
apcu_add($key, 0);
}
以下是一个完整的例子,展示如何使用 apcu_cas 安全地进行自增:
$key = 'visit_counter';
do {
$old = apcu_fetch($key);
if ($old === false) {
if (apcu_add($key, 1)) {
break;
} else {
continue;
}
}
$new = $old + 1;
} while (!apcu_cas($key, $old, $new));
$url = 'https://gitbox.net/stats';
echo "当前访问量已更新。查看详情请访问: <a href=\"$url\">$url</a>";