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


Posted in PHP onSeptember 25, 2009

在网上搜了搜,有两个办法但都不太好:一个是简单的以进程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 1212443 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+DBM的同学录程序(3)
Oct 09 PHP
PHP 选项及相关信息函数库
Dec 04 PHP
PHP mail 通过Windows的SMTP发送邮件失败的解决方案
May 27 PHP
PHP中输出转义JavaScript代码的实现代码
Apr 22 PHP
国外PHP程序员的13个好习惯小结
Feb 20 PHP
PHP 杂谈《重构-改善既有代码的设计》之三 重新组织数据
Apr 09 PHP
三个类概括PHP的五种设计模式
Sep 05 PHP
将博客园(cnblogs.com)数据导入到wordpress的代码
Jan 06 PHP
php带抄送和密件抄送的邮件发送方法
Mar 20 PHP
解决laravel5中auth用户登录其他页面获取不到登录信息的问题
Oct 08 PHP
laravel解决迁移文件一次删除创建字段报错的问题
Oct 24 PHP
宝塔面板在NGINX环境中TP5.1如何运行?
Mar 09 PHP
PHP 日常开发小技巧
Sep 23 #PHP
php程序之die调试法 快速解决错误
Sep 17 #PHP
火车采集器 免费版使出收费版本功能实现原理
Sep 17 #PHP
使用php来实现网络服务
Sep 15 #PHP
Discuz 6.0+ 批量注册用户名
Sep 13 #PHP
火车头discuz6.1 完美采集的php接口文件
Sep 13 #PHP
PHP 分页类(模仿google)-面试题目解答
Sep 13 #PHP
You might like
DC这些乐高系列动画电影你看过几部?
2020/04/09 欧美动漫
php 获取本地IP代码
2013/06/23 PHP
谈谈你对Zend SAPIs(Zend SAPI Internals)的理解
2015/11/10 PHP
PHP getName()函数讲解
2019/02/03 PHP
JQuery 学习笔记 选择器之一
2009/07/23 Javascript
JavaScript 页面编码与浏览器类型判断代码
2010/06/03 Javascript
Lazy Load 延迟加载图片的jQuery插件中文使用文档
2012/10/18 Javascript
js 火狐下取本地路径实现思路
2013/04/02 Javascript
jQuery判断checkbox(复选框)是否被选中以及全选、反选实现代码
2014/02/21 Javascript
jQuery中hasClass()方法用法实例
2015/01/06 Javascript
js创建对象的方式总结
2015/01/10 Javascript
jQuery中die()方法用法实例
2015/01/19 Javascript
JavaScript针对网页节点的增删改查用法实例
2015/02/02 Javascript
javascript设置文本框光标的方法实例小结
2016/11/04 Javascript
微信小程序使用navigateTo数据传递的实例
2017/09/26 Javascript
基于Two.js实现星球环绕动画效果的示例
2017/11/06 Javascript
在Vue项目中取消ESLint代码检测的步骤讲解
2019/01/27 Javascript
jQuery对底部导航进行跳转并高亮显示的实例代码
2019/04/23 jQuery
新手如何快速理解js异步编程
2019/06/24 Javascript
vue模块移动组件的实现示例
2020/05/20 Javascript
从零开始在vue-cli4配置自适应vw布局的实现
2020/06/08 Javascript
vue+node 实现视频在线播放的实例代码
2020/10/19 Javascript
Vue实现简单购物车功能
2020/12/13 Vue.js
Python实现判断一个字符串是否包含子串的方法总结
2017/11/21 Python
python爬取m3u8连接的视频
2018/02/28 Python
Python3.5内置模块之shelve模块、xml模块、configparser模块、hashlib、hmac模块用法分析
2019/04/27 Python
django model object序列化实例
2020/03/13 Python
TensorFlow keras卷积神经网络 添加L2正则化方式
2020/05/22 Python
python定义类的简单用法
2020/07/24 Python
python3列表删除大量重复元素remove()方法的问题详解
2021/01/04 Python
css3 flex布局 justify-content:space-between 最后一行左对齐
2020/01/02 HTML / CSS
Spartoo比利时:欧洲时尚购物网站
2017/12/06 全球购物
Dodax奥地利:音乐、电影、书籍、玩具、电子产品等
2019/08/31 全球购物
低端且暴利的线上线下创业项目分享
2019/09/03 职场文书
如何使用pdb进行Python调试
2021/06/30 Python
Python机器学习之决策树和随机森林
2021/07/15 Javascript