首页 > 编程知识 正文

进程信号量(php最大进程数)

时间:2023-05-03 06:53:31 阅读:82078 作者:443

PHP工艺研究

将PHP作为解释器运行是通过线程或进程实现的。 如果使用Apache,则可能使用多线程模型。 使用php-fpm意味着使用多进程模型。 这里用多进程模型进行说明)。 服务器每次收到请求时都启动PHP进程。 每个PHP进程平均消耗2米左右的内存。 (默认值最大为8米,可以设定参数。 独立的过程使PHP可以专注于自己的解释工作,程序员也可以从复杂的代码逻辑中走出来,无需担心资源冲突和各种锁定问题。 独立进程很好,但这要多进程或异步高速化的成本非常高(主要是开发难度)。 如果必须通过PHP实现多进程和异步,其实很容易做到。

PHP有很多第三方扩展。 例如,Swoole可以像节点一样异步PHP。 PHP官方扩展库pcntl_*可以轻松实现多进程。 虽然扩张很好,但是在实用上必须慎重。 方便的同时也带来风险。 例如,在多进程控制中,如果处理不好,程序容易发生死锁,CPU内存死机,服务器宕机。 异步回调的编码方式与PHP自身的编程思想有一定的差异,做不好也是灾难。

当然不是说太可怕,但是在实际的项目中,有很多必须考虑多进程和异步优化程序的情况。 在这里,列举了邮件和邮件等一般的例子“发送消息通知”。 在此,说明实际的方案。 企业需要向200瓦的用户发送邮件通知。 消息界面最多支持100次/秒的调用频率,而消息界面每次调用需要300毫秒。 如果在单进程中运行脚本,则发送邮件需要7天。 如果我们启动30个过程,每秒可以发送100封邮件,6个小时内可以发送,速度可以加快30倍。 优化方案确定后,让我们来看看如何在PHP中实现这样的脚本。

一、pcntl扩展入门;

有关使用pcntl扩展创建多进程的详细信息,请参见以下代码

函数演示(阵列$电话列表) $ CNT=计数) $电话列表); //测试数组大小

$slice=30; //需要调用的进程数

$ master=array _ chunk ($电话列表,地址) $ CNT/$片断);

$儿童列表=[ ]; wile($slice=0) )。

{

$ PID=pcntl _福克(; if($PID0) {

$儿童列表[ $ PID ]=1; //$pid0表示当前正在运行父进程的代码

//在这里什么也不做比较好。 每次执行pcntl_fork时,都会执行这里的代码。

//这里的代码执行结束后,将$pid设定为0,jump到pcntl_fork代码后,重新进行判断

}elseif($PID==0) )//在此写下我们的逻辑

福克斯($ master [ $ slice ] as $ val

//这里发生邮件

echo sprintf (' % schild : % srn ',$slice,$val );

//子进程运行结束后,必须关闭;

exit (;

}else

//即使程序发生错误,也需要关闭程序

exit (;

}

$slice----;

//等待所有子进程结束后再回收资源

while (! 空白($儿童列表) )

$子儿童身份证=pcntl _ wait ($状态); if ($儿童id0) {成套} $儿童列表[ $儿童id ] };

}

}

(/**运行结果如下,phone不连续

Slice id:19、手机:66558

Slice id:23、手机:79921

Slice id:19、手机:66559

Slice id:23、手机:79922

Slice id:19、手机:66560

Slice id:23、手机:79923

Slice id:19、手机:66561

Slice id:23、手机:79924

Slice id:19、手机:66562

Slice id:23、手机:79925

*/

通过pcntl扩展,一些代码使用多进程将消息通知功能提高了30倍。 但是,明明是这么简单的多进程编码,为什么我开始用文章来表达这么复杂的事情呢?

难点还是进程间通信。 这是因为向用户发送邮件的各个子进程相对独立,进程之间没有通信,不相互传递数据的状态。 不会发生资源断开和锁定的问题。 如果需求发生变化,就需要按照用户的活跃度从高到低的顺序给用户发送邮件。 我该怎么办?

简单地说,一个盘子里有30个苹果,所以需要发给30人,3人有分发苹果的责任。 最简单的方法是先把苹果分成三份,三个人各分一份,这样可以马上发货。 但是,如果要按照苹果的大小顺序发,先发大苹果,这个时候我们不能分成三份,只能三个人互相挣现在最大的,很容易就吵起来。 我该怎么办? 最常见的方法是用一个工具把所有的苹果

按由大到下的顺序放在里面,每次只能取一个,这样就解决了资源抢占的问题。

关于进程间资源抢占的问题非常的复杂,编码难度非常高,这也是为什么很少使用PHP跑多进程的原因。当需要用到多进程时我们更愿意去使用Python或者Java,它们对多线程封装的更好。需要重点说的是PHP并不是不能写多进程的程序,也不是像其他人说的不稳定,而是编码费时,维护成本高。

二. 进程间通信

常见的进程通信方式有:消息队列、共享内存与信号量、管道、socket,我将一一举例说明。

消息队列

『消息队列』是在消息的传输过程中保存消息的容器。消息队列管理器相当于消息发送者和接收者的中介。消息队列的主要目的是创建路由并且保证消息可靠传递;如果发送消息时接收者不可用,消息队列会保留消息,直到有人接收它。

消息队列可提供临时存储的功能并且能保证消息可靠的传递,我们正好使用它实现进程间通信。当然消息队列不单单用于进程间通信,他的应用领域非常广。比如消息队列非常适用于解决消费者和生产者的问题,因为生产者和消费者之间总会存在『速度差』。比如生产者突然少了10个,两边处理的速度就会不平衡,会导致排队阻塞,服务不可用。这肯定不是我们想看到的,如果这时候引入消息队列将两个系统解耦,无论谁慢了都不会影响整体业务。

function demo(array $phoneList){ global $msgQueue;

$cnt = count($phoneList); //测试数组大小

$slice = 3; //需要调用的进程数量

$childList = []; //主进程先发送一条消息,告诉子进程可以发送第一条短信了

msg_send($msgQueue,MSG_TYPE,0); while($slice >= 0)

{

$pid = pcntl_fork(); if($pid > 0){

$childList[$pid] = 1; //父进程什么都不用做

}elseif($pid == 0){ //子进程不停的请求,直到所有短信发送完成

while(msg_receive($msgQueue,MSG_TYPE,$msgType,1024,$message))

{ if($cnt>intval($message))

{

printf("Slice id:%s,phone:%s rn",$slice,$phoneList[$message]);

$message = $message + 1;

msg_send($msgQueue,MSG_TYPE,$message);

}else

{ //通知其他进程一切都结束了

msg_send($msgQueue,MSG_TYPE,$cnt); exit();

}

}

}else

{ //程序发生错误也需要关闭程序

exit();

}

$slice--;

} // 等待所有子进程结束后回收资源

while(!empty($childList)){

$childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]);

}

}

}const MSG_TYPE = 1;//创建消息队列$id = ftok(__FILE__,'m');

$msgQueue = msg_get_queue($id);

demo(range(0,900));/**运行结果,按大小输出

Slice id:1,phone:895

Slice id:1,phone:896

Slice id:2,phone:897

Slice id:3,phone:898

Slice id:3,phone:899

**/

共享内存与信号量

『共享内存』很容易理解,就是在内存中找一块区域,所有进程都能读写。『信号量』是系统提供的一种原子操作,进程在开启信号和结束信号之间拥有共享内存的『绝对占有』权,这样能有效的防止多个进程读取同一个资源时发生死锁。

function demo(array $phoneList){ global $shareMemory; global $signal;

$cnt = count($phoneList); //测试数组大小

$slice = 3; //需要调用的进程数量

$childList = []; while($slice >= 0)

{

$pid = pcntl_fork(); if($pid > 0){

$childList[$pid] = 1; //父进程什么都不用做

}elseif($pid == 0){ while(true)

{ // 标记信号量,这里被我承包了

sem_acquire($signal); //检测共享内存是否存在

if (shm_has_var($shareMemory,SHARE_KEY)){ //从共享内存中拿数据

$val = shm_get_var($shareMemory,SHARE_KEY); if($val>=$cnt)

{

sem_release($signal); break;

}else

{

printf("Slice id:%s,phone:%s rn",$slice,$phoneList[$val]);

$val ++; //再将数据写入共享内存

shm_put_var($shareMemory,SHARE_KEY,$val);

}

}else{ // 无值会,先初始化

shm_put_var($shareMemory,SHARE_KEY,0);

} // 用完释放

sem_release($signal);

} exit();

}else

{ //程序发生错误也需要关闭程序

exit();

}

$slice--;

} // 等待所有子进程结束后回收资源

while(!empty($childList)){

$childPid = pcntl_wait($status); if ($childPid > 0){ unset($childList[$childPid]);

}

}

}const SHARE_KEY = 1;// 创建一块共享内存$shm_id = ftok(__FILE__,'a');

$shareMemory = shm_attach($shm_id);// 创建一个信号量$sem_id = ftok(__FILE__,'b');

$signal = sem_get($sem_id);

demo(range(0,900));// 释放共享内存与信号量shm_remove($shareMemory);

sem_remove($signal);/**运行结果,按大小输出

Slice id:1,phone:775

Slice id:3,phone:776

Slice id:3,phone:777

Slice id:3,phone:778

Slice id:0,phone:779

Slice id:0,phone:780

**/

管道

管道是比较常用的进程间通信手段,管道又分为匿名管道(pipe)与具名管道(mkfifo),匿名管道只能用于具有亲缘关系的进程间通信,而具名管道可以用于同一主机上任意进程。

pipe与mkfifo的主要差别是mkfifo会创建一个特殊的FIFO物理文件,这个FIFO文件其他进程都可以像读写一般文件一样读写。再写下去文章就太长了,之后写下一篇吧。

未完待续……

PS:所有代码都放到了GitHub:php_thread_demo

沟通和互动以及更多干货,欢迎关注新浪微博:@阿里云云栖社区

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。