前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP进程通信之共享内存+UNIX Socket(二十四节)

PHP进程通信之共享内存+UNIX Socket(二十四节)

作者头像
老李秀
发布2020-03-11 10:44:13
1.2K0
发布2020-03-11 10:44:13
举报
文章被收录于专栏:可能是东半球最正规的API社区

大家好,老李是我;我永远都爱这样的我。

有人跟我说:「老李,你再也不是以前的你了」。他说这句话的时候,我仿佛感觉到了当年马克·查普曼在一枪干掉了约翰·列侬后,对着列侬的尸体说:“ 你变了 ”...

我怎么能变呢,我还是那个老李:就算整个晋西北乱成了一锅粥,我还是我,是颜色不一样的烟火,当年那个十里八乡有名的俊后生,当年雪上草地里背过锅,二营长意大利炮扛上来,老子打得就是他精锐,别说它一个板田联队,有一个师老子就TM敢打太原。你好好看看我哪儿变了?

硬核,风骚,还带着那么一丝丝小帅。就算说变,那也仅仅可能是在向「真*谢顶道人」的方向变,真到那个时候我就改号为「秃顶法师」。

恒河水一口下了肚,今天要搞一波儿流。还是有同学整不明白进程间通信的意义啊,结合WM简单说一种情况,众所周知WM的进程模型是Master-Worker类型的,具体表现大概就是Master fork出Worker,每个Worker持有一个Event-Loop,这是一种与Nginx十分相似的进程模型,但我估计不少老哥平时用WM没准能开大几十甚至上百个Worker进程,虽然用起来是没问题,但总归很奇怪,你见过Nginx进程开这么多的么(你要说你服务器是128核那当我没说)?所以李亮在WM问答社区里推荐一种做法就是再在WM后面接入一群任务服务器,前后之间利用异步方式通信,大概就是类似于挡在前面这一坨WM服务器负责高吞吐数据转发,业务逻辑的处理则是交给后面的一坨任务服务器。其实吧,用过Swoole多进程模式的铁子们应该比较熟悉,其实就是Swoole里Worker进程与Tasker进程的故事。

上面叨逼叨这么一堆,你应该意识到如果WM自己本身带一组Task进程也挺好的,Worker进程就可以靠高性能的进程间通信与Task进程数据交互。

用线程不好吗?行算泥狠,我后面补一个PHP多线程系列

在关于进程间通信这里,一牵涉到共享内存就脑袋大,多个进程/线程访问同一块儿共享内存,你就是抠脚趾头都能预料到读写锁的问题,所以还得有个信号量来「相濡以沫」一下:

一个是坦克,另一个是坦克行车记录仪

一个是航母,另一个是航母雨刮器

一个是潜艇,另一个是潜艇风湿活血理疗仪

一个是飞机,另一个是飞机耐磨防刮漆

也就是说信号量和共享内存是分不开的,要用也是搭配着用。*NIX的一些书籍中甚至不建议新手轻易使用这种进程间通信的方式,因为这是一种极易产生死锁的解决方案。共享内存顾名思义,就是一坨内存中的区域,可以让多个进程进行读写。为了解决这个问题才引入了信号量,信号量是一个计数器,是配合共享内存使用的,一般情况下流程如下:

  • 当前进程获取将使用的共享内存的信号量
  • 如果信号量大于0,那么就表示这块儿共享资源可以使用,然后进程将信号量减1
  • 如果信号量为0,则进程进入休眠状态一直到信号量大于0,进程唤醒开始从1一个进程不再使用当前共享资源情况下,就会将信号量减1。

这个地方,信号量的检测并且减1是原子性的,也就说两个操作必须一起成功,这是由系统内核来实现的。在PHP中,信号量和共享内存先后一共也就这几个函数:

其中sem前缀的是信号操作相关函数,shm前缀是共享内存相关函数。

代码语言:javascript
复制
<?php
// sem key
$sem_key = ftok( __FILE__, 'b' );
$sem_id = sem_get( $sem_key );
// shm key
$shm_key = ftok( __FILE__, 'm' );
$shm_id = shm_attach( $shm_key, 1024, 0666 );
const SHM_VAR = 1;
$child_pid = [];
// fork 2 child process
// 下面逻辑很简单,就是两个子进程去操作同一块共享内存
// 每个进程在试图读或者写共享内存前,都必须先获得锁
// 当然,操作完毕后,一定记得要释放锁
for( $i = 1; $i <= 2; $i++ ){
    $pid = pcntl_fork();
    if( $pid < 0 ){
        exit();
    } else if( 0 == $pid ) {
        // 子进程中,在操作shm共享内存前,首先获取sem锁
        sem_acquire( $sem_id );
        if( shm_has_var( $shm_id, SHM_VAR ) ){
            $counter = shm_get_var( $shm_id, SHM_VAR );
            $counter += 1;
            shm_put_var( $shm_id, SHM_VAR, $counter );
        } else {
            $counter = 1;
            shm_put_var( $shm_id, SHM_VAR, $counter );
        }
        // 释放锁,一定要记得释放,不然就一直会被阻锁死
        sem_release( $sem_id );
        exit;
    } else if( $pid > 0 ) {
        $child_pid[] = $pid;
    }
}
while( !empty( $child_pid ) ){
    foreach( $child_pid as $pid_key => $pid_item ){
        pcntl_waitpid( $pid_item, $status, WNOHANG );
        unset( $child_pid[ $pid_key ] );
    }
}
// 休眠2秒钟,2个子进程都执行完毕了
sleep( 2 );
echo '最终结果'.shm_get_var( $shm_id, SHM_VAR ).PHP_EOL;
// 记得删除共享内存数据,删除共享内存是有顺序的,先remove后detach,顺序反过来php可能会报错
shm_remove( $shm_id );
shm_detach( $shm_id );

然而,今天压箱底的是AF_UNIX所代表的UNIX本地socket方式。当然了人家是先有socket后有这种UNIX Socket,这玩意也是后来满满发展来的,你可以理解为一开始socket是面向跨机器的网络通信,后来发现这玩意纯用在本地搞搞单机版跨进程通信效果也贼不错,而且这种本地版本的socket跑在127.0.0.1地址上,你别看是socket然而实际上一不经过网卡、二也没有网络协议解析那些乱七八糟的,这是一种可靠的连接服务。

其实有些老哥在折腾MySQL的时候应该注意到了,一个叫做mysql.sock的文件;或者折腾php-fpm与Nginx的时候,有个php-fpm.sock。一般约定俗成的话,这种后缀为sock的文件就是UNIX本地socket。

拿php-fpm里这个sock来说,当你把Nginx服务器与php-fpm部署在同一台机器上的时候,你完全可以考虑使用本地socket的方式让Nginx与php-fpm进行数据交换,很明显这种省略掉网络开销的通信方式应该是更高效的,不过我曾经在网上看到过一个中论调,大概是说「php-fpm这种unix socket通信方式不稳定」而且还感染了相当一批人,但是通篇也没有看到这种论调的论据是什么:

言论自由没问题,言论是否要负责呢?

如果你知道这种「不稳定」的原因,可以后台提供一下,我下篇文章打补丁。

由于前面我们说过socket相关的操作函数,所以下面的demo你们看起来应该是不费吹灰之力的,但是你们一定要把注意力分配到注释上:

代码语言:javascript
复制
/***********  一侧代码  **********/
<?php
// 这里要注意的是,第一个参数用AF_UNIX表示要创建一个本地unix域
// 但是最后一个参数。。。给老子折腾的不清,只有用0才不会报错
// 但是,他TM文档里压根没提这档子事!fk
$listen_socket = socket_create( AF_UNIX, SOCK_STREAM, 0 );
// 这会儿就不需要把socket bind到IP+PORT上了
// 而是bind到一个sock文件上
$file = "./server.sock";
socket_bind( $listen_socket, $file );
socket_listen( $listen_socket );
while ( true ) {
    $connection_socket = socket_accept( $listen_socket );
    $ret = socket_recv( $connection_socket, $recv_content, 2048, 0 );
    echo $recv_content;
    $encode_ret = "higood";
    socket_write( $connection_socket, $encode_ret, strlen( $encode_ret ) );
}

/***********  另一侧代码  **********/
<?php
$conn_socket = socket_create( AF_UNIX, SOCK_STREAM, 0 );
$file = "./server.sock";
socket_connect( $conn_socket, $file );
socket_write( $conn_socket, "HI,I am coming." );
socket_recv( $conn_socket, $recv_content, 2048, 0 );
echo $recv_content;

大概就这样,能读能写,双向通信,成熟稳定,这瓜保熟。这代码你要是说你看不懂,村上春树都能被你气得直接下乡种树。

  • 卧槽,老李,进程间通信已经完毕了,《PNP》是不是要完结撒花了?
  • ...屁~我突然又临时想到有些「缺要查,有些「漏要补」...还差最后两个章节
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档