从并发处理谈PHP进程间通信(二)System V IPC

前言

进程间通信是一个永远的话题,我的上一篇文章通过一个并发循环ID生成器的实现介绍了如何使用外部介质来进行进程间通信:从并发处理谈PHP进程间通信(一)外部介质 。介绍的几种方法适用于各种语言,但是他们都依赖于一种外部介质,文化的读写有瓶颈,mysql 和 redis 会挂掉或连接超时,归根结底总觉得在 HACK;

对于进程间通信,每一个完备的语言都应该有对应的处理方式,而 PHP 对应的则是一族对 UNIX SYSTEM V包装的函数,包括信号量(semaphore)、共享内存(shared memory)和消息队列(msg queue)的操作。

它的安装和使用非常简单,在编译 PHP 时添加 --enable-sysvsem --enable-sysvshm --enable-sysvmsg 参数就可以,当然 Windows 上无法使用。

今天我们仍旧使用上一篇文章的例子来介绍 PHP 内部实现的进程间通信,在了解它们的具体使用之前,先简单介绍一下信号量、共享内存、消息队列的概念。

Unix System V IPC

信号量

信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。

一般说来,为了获得共享资源,进程需要执行下列操作:

  1. 获取控制共享资源的信号量的值;
  2. 若值为正,进程将信号量减1,进程操作共享资源,进入步骤4;
  3. 若值0,则拒绝进程使用共享资源,进程进入睡眠状态,直至信号量值大于0后,进程被唤醒,转入步骤1;
  4. 当进程不再使用共享资源时,将信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程;

信号量的使用可以类比为:

一个房间必须用钥匙才能开门,有N把钥匙放在门口,拿到钥匙开门进入房间,出来时将钥匙放回并告知等待的人去取钥匙开门。 此例中,钥匙的数量限制了同一时间内在房间的最大人数。房间即共享资源,钥匙是信号量,而想进入房间的人则是多个进程。

信号量有二值和多值之分,一般共享资源都不允许多个进程同时操作,多使用二值信号量。

共享内存

为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。共享内存可以比喻成一块公用黑板,每个人都能在上面留言,写东西。

到于共享内存,我们一定要关心其生存周期:System V 共享内存区域对象是随内核持续的,除非显式删除共享内存区域对象,即使所有访问共享内存区域对象的进程都已经正常结束,共享内存区域对象仍然在内核中存在,在内核重新引导之前,对该共享内存区域对象的任何改写操作都将一直保留。

消息队列

消息队列是一条公共消息链,消息存取一般为先进先出(FIFO),能实现多个进程对消息的原子操作和异步存取。消息队列的应用十分广泛,不光是进程间通信,流程异步化、解耦方面也应用广泛。

消息队列则相当于一条流水线的一段,上层有多个工人把产品放入,下层有多个工人将产品取出加工。

本文的实现不包括消息队列的使用,但对于消息队列实现互斥锁,这里给出一个思路:先给消息队列初始化一个值,并发进程竞争获取此值,获取到值的进程进行共享资源的处理,进程不再共享资源时,再将此值放入队列,通过队列的原子性来保证同时只有一个进程访问共享资源。

函数介绍

ftok

int ftok ( string $pathname, string $proj )

ftok将一个路径 pathname 和一个项目名(必须为一个字符), 转化成一个整数形的 System V IPC 键,本文介绍的 System V 通信方式都是基于此键来完成的,此ID 值也可以自己指定一个 INT 型来确定,不必要使用 ftok 获取;

需要注意的是:ftok 的结果是通过文档的索引节点号来计算获取的,而文件的删除重建会导致其索引节点号变动,所以即使是相同的文件名,也可能会导致获取到的 IPC 键不同,所以需要尽量保证 $pathname 不变动;

semaphore函数

  • resource sem_get ( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] ) 获取或生成一个信号量标识,我们注意其 max_acquire 值为 1,即保证同时只有一个进程能获取到它;auto_release 为 1 ,保证进程在非正常情况退出时能释放此信号量;
  • bool sem_acquire ( resource $sem_identifier [, bool $nowait = false ] ) bool sem_release ( resource $sem_identifier ) 获取/释放一个信号量,注意获取信号量的 $nowait 为false,使进程在获取信号量失败后进行进程等待即可。

shared_memory函数

  • resource shm_attach ( int $key [, int $memsize [, int $perm = 0666 ]] )
  • bool shm_detach ( resource $shm_identifier ) 连接/断开 与 共享内存段的连接 $memsize, 以字节 byte 为单位;需要注意,在第一次使用 $key 连接内存段创建时,会初始化内存大小和权限,后续再连接时,这两个参数会被忽略。
  • bool shm_put_var ( resource $shm_identifier , int $variable_key , mixed $variable )
  • mixed shm_get_var ( resource $shm_identifier , int $variable_key ) 向共享内存内写入或读取一个变量,需要注意变量 key 只能是 int 型;

代码实现

function getCycleIdFromSystemV($max, $min = 0) {
        $key = ftok('/tmp/cycleIdFromSystemV.tok', 'd');
        $var_key = 0;
        $sem_id = sem_get($key);
        $shm_id = shm_attach($key, 4096);
    
        if (sem_acquire($sem_id)) {
            $cycle_id = intval(shm_get_var($shm_id, $var_key));
            $cycle_id++;
            if ($cycle_id > $max) {
                $cycle_id = $min;
            }
            shm_put_var($shm_id, $var_key, $cycle_id);
    
            shm_detach($shm_id);
            sem_release($sem_id);
    
            return $cycle_id;
        }
        
        return false;
    }

小结

我们发现 PHP 对信号量和共享内存封装得很好,使用起来非常简单。除此之外,PHP 的类库 Sync 将常用 IPC 方法封装成为类,能实现跨平台的使用,感兴趣的可以了解使用一下。

当然进程间通信的方式和种类有很多,本文介绍的 id 递增只是很简单的一种,不过,知道了方法,再去把这些方法改造成为其他种类也就不难了。

如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。文章一直在更新,欢迎 关注

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏纯洁的微笑

架构师带你玩转分布式锁

大多数互联网系统都是分布式部署的,分布式部署确实能带来性能和效率上的提升,但为此,我们就需要多解决一个分布式环境下,数据一致性的问题。

712
来自专栏owent

关于BUS通信系统的一些思考(二)

虽然我很不愿意再设计一套BUS系统,但是现有的一些确实都没有特别符合我的口味的。所以还是尝试设计一个出来。

813
来自专栏陈纪庚

不到50行代码实现一个能对请求并发数做限制的通用RequestDecorator

在开发中,我们可能会遇到一些对异步请求数做并发量限制的场景,比如说微信小程序的request并发最多为5个,又或者我们需要做一些批量处理的工作,可是我们又不想同...

702
来自专栏Golang语言社区

linux 内核同步机制使用

Linux 内核中的同步机制:原子操作、信号量、读写信号量、自旋锁的API、大内核锁、读写锁、大读者锁、RCU和顺序锁。 1、介绍 在现代操作系统里,同一时间...

2574
来自专栏IT技术精选文摘

RPC原理及实现

1 简介 RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明...

4769
来自专栏美团技术团队

缓存那些事

前言 一般而言,现在互联网应用(网站或App)的整体流程,可以概括如图1所示,用户请求从界面(浏览器或App界面)到网络转发、应用服务再到存储(数据库或文件系统...

4804
来自专栏用户2442861的专栏

使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史

http://blog.csdn.net/hguisu/article/details/38638183(牛逼100多名)

911
来自专栏程序员互动联盟

linux设备驱动第五篇:驱动中的并发与竟态

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurr...

33010
来自专栏python3

python3--协程,greenlet模块,gevent模块

之前学习了线程、进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。按道理来说我们已经算是把cpu的利用率提高很多了。但是我们知...

992
来自专栏贾老师の博客

【笔记】高性能服务器设计

1054

扫码关注云+社区