PHP下通过系统信号量加锁方式获取递增序列ID

老晨大大_8319

老晨大大_8319

2016-06-13

988人浏览

原创

在网上搜了搜,有两个办法但都不太好:一个是简单的以进程ID+时间戳,或进程ID+随机数来产生近似的唯一ID,虽简单但对于追求“完美”的我不愿这样凑合,再说Apache2以后进程会维持相当长得时间,生成的ID发生碰撞的几率还是比较大的;第二个思路是通过Mysql的自增字段,这个就更不能考虑了,效率低不说,我的设计里压根就没数据库。
递增ID的获取是个过程:
1. 从全局某个存储中读取ID
2. 给ID加1
3. 将ID重新存入全局存储
在多进程或线程的程序中需要将上述3步作为单步的原子操作,才能保证ID的唯一。
Java中很好解决,这是因为Java程序大多以多线程方式运行,每个线程都能共享Java进程中的变量,并能方便的加线程锁控制线程的运转同步。在PHP中ID全局存储没问题,可以放在session中,大不了放在文件中,但进程间同步就是问题了。
实际上进程调度、管理是操作系统内核必须实现的功能,今天介绍的信号量(也称为信号灯)就是在Unix/Linux上解决进程同步的一项技术。
信号灯原是用在铁路上的管理机制,我们今天看到的铁路大多是双线并行,但有的路段受山势、地形影响只有单条铁轨,必须保证同一时间只能有一列火车运行通过这些路段。早先铁路上就是用信号灯来管理的:没有火车经过时,信号等处于闲置状态,一旦有火车进入此路段,信号灯即变为在用状态,其他的火车经过时就需要等待,等待先前的火车驶出路段信号等变为闲置后,才能进入此路段,一旦又有火车进入,信号灯又变为繁忙......,以此来保障铁路运行的安全畅通。
Unix系统就像铁路管理局控制信号灯一样管理控制信号量的状态,因此也可以这样说信号量是由内核管理的,信号量不仅能控制进程间的同步,同样可以控制线程间的同步。
信号量属于系统进程间通讯技术(IPC),今天我们只从PHP角度介绍信号量的使用,有关IPC的技术细节可参考Stevens的权威著作《UNIX网络编程第二卷 进程间通信》。
先看最终的代码:

复制代码 代码如下:


php
// ---------------------------------------------------
// 递增序列号ID(1~1000000000)
//
// ID存储在共享内存中(shared memory),通过信号灯(semaphore)同步
// ---------------------------------------------------
$IPC_KEY = 0x1234; //System V IPC KEY
$SEQ_KEY = "SEQ"; //共享内存中存储序列号ID的KEY
//创建或获得一个现有的,以"1234"为KEY的信号量
$sem_id = sem_get($IPC_KEY);
//创建或关联一个现有的,以"1234"为KEY的共享内存
$shm_id = shm_attach($IPC_KEY, 64);
//占有信号量,相当于上锁,同一时间内只有一个流程运行此段代码
sem_acquire($sem_id);
//从共享内存中获得序列号ID
$id = @shm_get_var($shm_id, $SEQ_KEY);
if ($id == NULL || $id >= 1000000000)
{
$id = 1;
}
else
{
$id++;
}
//将"++"后的ID写入共享内存
shm_put_var($shm_id, $SEQ_KEY, $id);
//释放信号量,相当于解锁
sem_release($sem_id);
//关闭共享内存关联
shm_detach($shm_id);
echo "序列号ID:{$id}";
?>


009行,定义了一个16进制的整形KEY,在PHP中只支持System V的IPC机制,需要通过一个KEY关联到指定的资源(消息队列、信号量、共享内存)。
010 行,定义了一个在共享内存中存储递增ID的KEY,这是PHP对System V共享内存的闲置:需要通过类似hashtable的KEY-VALUE方式存储变量。在上面的代码中使用共享内存做ID的存储容器,也可以换为 Session、文件等其他机制,本文重点是信号量,有关共享内存的知识以后在讲(别忘了前面推荐的那本书)。
013行,获得系统中的以1234为KEY的信号量,如果系统中没有就创建一个。
015行,同13行相似,获得系统中的以1234为KEY的共享内存,如果系统中没有就创建一个,第二个参数64表示创建64bytes大小的共享内存。
018~034 行,同步代码区,当一个进程或线程执行sem_acquire函数占有了信号量,到它调用sem_release函数释放信号量的过程内,其他进程或线程执行到sem_acquire会阻塞。021行从共享内存中获得ID,函数shm_get_var前缀"@"是为了屏蔽出错信息(第一次执行时,共享内存中并没有以"SEQ"为KEY的数据,会在页面上打印警告信息)。
其他语句非常简单,不需多讲。
程序编好后,访问这个PHP页面,会递增的输出数字。
我们可以通过系统命令ipcs查看在程序创建的信号量和共享内存:
$ ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00001234 1212412443 www-data 666 64 0
------ Semaphore Arrays --------
key semid owner perms nsems
0x00001234 163841 www-data 666 3
------ Message Queues --------
key msqid owner perms used-bytes messages
前两段分别是共享内存和信号量,0x00001234既是我们创建的KEY。
也可以通过命令ipcrm删除:
$ ipcrm -M 0x00001234 #删除共享内存
$ ipcrm -S 0x00001234 #删除信号量
---------------------------------------------
PHP手册中关于IPC的资料非常少,这点也不难想象,Stevens已经在十几年前讲得透透的东东,在PHP中只是包装了一下,还有多少必要去深入说明呢?
文本只是借着ID说了说信号量的使用,如果您有更简单的生成自增ID的办法,还望赐教。
可能有朋友还想了解信号量的执行效率,我这里用一句过时的流行语总结: 相当的快。

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

2026.11.12

80

15

Go-Zero安装与goctl环境配置教程
Go-Zero安装与goctl环境配置教程

本专题围绕Go-Zero的安装与工具链配置展开,讲清Go版本要求、goctl安装方式、代理镜像、protoc依赖与环境变量设置,说明多版本共存、升级回退与CI流水线安装的做法,并整理安装阶段的常见报错与验证步骤。

2026.11.12

60

15

Go-Zero零基础入门教程
Go-Zero零基础入门教程

本专题整理Go-Zero的入门知识,从框架定位、环境准备到跑通第一个API服务,讲清目录分层、配置加载、日志与中间件的基本用法,并对比Gin、Kratos的差异,帮助Go开发者建立整体认知,判断它是否适合当前项目。

2026.11.12

40

15

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

2026.11.06

140

12

Postman请求头Headers怎么设置
Postman请求头Headers怎么设置

围绕Postman HTTP请求头配置展开,讲解Headers选项的具体使用方式,包括Key和Value参数填写、请求头信息添加、常见Header字段设置及请求头修改方法,帮助用户解决接口调试过程中遇到的请求头配置问题。

2026.11.06

120

13

Postman接口请求发送教程
Postman接口请求发送教程

详细介绍Postman接口请求的创建与发送方法,涵盖GET和POST请求、URL配置、请求参数、Headers请求头、Body请求体设置以及响应结果查看,帮助用户掌握Postman接口请求的完整操作流程。

2026.11.06

120

10

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

2026.11.05

100

12

DBeaver安全防护教程大全
DBeaver安全防护教程大全

DBeaver安全防护专题,围绕数据库连接安全、账号密码保护和访问权限管理展开,整理SSL/TLS加密连接、SSH隧道、数据库凭据保存及证书验证等配置方法,同时介绍敏感信息保护、远程连接风险和高权限账号使用等注意事项,帮助用户提高数据库日常管理的安全性。

2026.11.05

80

12

DBeaver连接创建与配置教程
DBeaver连接创建与配置教程

本专题聚焦DBeaver数据库连接配置与管理,覆盖从新建连接、选择数据库类型到填写连接参数和验证连接的完整流程。针对MySQL、PostgreSQL、SQL Server等常见数据库,整理相应的连接设置方法,并提供驱动缺失、认证失败和连接异常等问题的排查方向。

2026.11.04

100

16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习