首页 > 代码库 > PHP高性能开发-多进程开发
PHP高性能开发-多进程开发
硬件多核时代的软件业
以前计算能力的提升一直在摩尔定律的指引下,沿着提升CPU时钟频率这条道路前进,从初期的几十MHz到如今的几GHz。但是,进入2002年以 来,CPU提升主频的困难越来越大,因为主频的提升带来了散热和功耗的大幅增加等问题。几年前,英特尔和AMD都调整了研究方向,开始研究在同一CPU中 放置多个执行内核。
尽管多核是一种硬件技术,但硬件和软件是相互依存的,硬件只是一种物质基础,只有有了软件的支持,才能使硬件拥有用武之地。如今,多核的优势已成共识:一在于“降低功耗”,解决了以往靠主频提高带来的后遗症问题;
二在于计算性能更强,能够满足用户同时进行多任务处理和多任务计算环境的要求。要发挥多核的这些优势,相应软件的支持必不可少。其中,最主要的就是软件程序要能够并行处理。
并发和并行的关系
并发和并行的区别就是一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生.并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。
来个比喻:并发和并行的区别就是一个人同时吃三个馒头和三个人同时吃三个馒头。
进程与线程
什么是进程(Process),普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
现代的操作系统几乎都是多任务操作系统,我们通常使用的计算机中只有一个CPU,也就 是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简 单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来 CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。
如果一台计算机有多个CPU,情况就不同了,如果进程数小于CPU数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行。但如果进程数大于CPU数,则仍然需要使用并发技术。
如果在多CPU计算机中只运行一个进程(线程),就不能发挥多CPU的优势。那么在多CPU的硬件条件下,一个进程是不是会在多个CPU上运行呢?
其实这还是要明白 进程是系统进行资源分配和调度的单位 线程是操作系统分配处理器(CPU)时间的基本单元,是系统中最小的执行单元。一个进程被分配了独立的资源空间,但里面真正执行的还是一个一个线程,在运行一个进程的时候,它里的若干线程会被空间的CPU调度执行。 所以一个进程会在多CPU上运行,也就是你在一个多CPU上只行动一个程序,也会看到多CPU在不停的切换
动态语言的现状
在Python,Ruby中,如果我们愿意,我们完全可以像大多数的c,c++,java开发者一样,随时随地的使用线程。但问题是,Ruby,Python使用了一个Global Interpreter Lock(aka GIL).这个GIL是一个保护数据完整性的锁机制。GIL每次只允许数据被一个线程修改,因此不让线程损坏数据,也不允许其真正的并发运行。这就是为什 么有些人说,Ruby和Python并不具备真正的并发性。
多进程或者fork进程 ,这是使用Ruby和Python,PHP并发最常用的解决方案。因为默认的语言并没有能力实现真正的并发,或者因为你想避免线程编程的挑战,你可能想去开启更多的进程。如果你并不想在进程间共享状态,这是很容易的。[
大多数的编程语言都不容易实现并发,函数式语言简化了并行开发,如Erlang,Scala,Lisp,Clojure.这得益于它既不用共享内存,也不会产生副作用(side effect)的函数。
PHP多进程方法
1.exec或system
2.popen会创建一个管道来连接该进程,然后使用fread/fgets/stream_get_contents来读取该进程返回的结果。跟 exec或system之类的函数不同的是,exec会等待命令执行完成,再运行下面的代码,但popen不会。proc_open又更加强大一些,支持 stdin和stdout,路径设置等等。
3.PHP有一组进程控制函数(编译时需要 –enable-pcntl与posix扩展),使得php能在*nix系统中实现跟c一样的创建子进程、使用exec函数执行程序、处理信号等功能。 PCNTL使用ticks来作为信号处理机制(signal handle callback mechanism),可以最小程度地降低处理异步事件时的负载。何谓ticks?Tick 是一个在代码段中解释器每执行 N 条低级语句就会发生的事件,这个代码段需要通过declare来指定。
PHP实现守护进程
编写Daemon程序有一些基本的规则,以避免不必要的麻烦。
1、首先是程序运行后调用fork,并让父进程退出。子进程获得一个新的进程ID,但继承了父进程的进程组ID。
2、调用setsid创建一个新的session,使自己成为新session和新进程组的leader,并使进程没有控制终端(tty)。
3、改变当前工作目录至根目录,以免影响可加载文件系统。或者也可以改变到某些特定的目录。
4、设置文件创建mask为0,避免创建文件时权限的影响。
5、关闭不需要的打开文件描述符。因为Daemon程序在后台执行,不需要于终端交互,通常就关闭STDIN、STDOUT和STDERR。其它根据实际情况处理。
另一个问题是Daemon程序不能和终端交互,也就无法使用printf方法输出信息了。我们可以使用syslog机制来实现信息的输出,方便程序的调试。在使用syslog前需要首先启动syslogd程序,关于syslogd程序的使用请参考它的man page,或相关文档,我们就不在这里讨论了。
进程间通信IPC
Linux下进程间通信的几种主要手段简介:
- 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
- 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号 给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数 是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
- 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
- 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
- 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
注意事项
- PHP的内存管理机制
- 进程IO异常
- 资源fork问题
- 通信的复杂度
- 进程的僵尸回收机制
具体的例子是
1、最好结合cronjob来定时跑脚本,这样即使你的代码没有管理好内存,也不要紧,跑完一次就释放掉了。
2、对于必须常驻进程的脚本,一定要在while (1) {} 这样一个死循环里面运行代码。这样只要代码不出状况,脚本就不会停止。
3、echo 不能用,而是用log 代替。用写日志的方法代替echo。因为echo 是向屏幕输出一个字符,如果没有任何输出的对象,就会报一个致命错误。
4、如果MYSQL,要每次重新连接MYSQL或者至少使用时判断连接。因为你的脚本运行期间难保mysql不会重启,一旦重启,之前连接资源就失效了,会报这样一个错误:mysql has go away。
5、新产生的变量,没用了要马上释放。
6、 如果要访问文件,首先要 clearstatcache, 否则很有可能会不精确的统计,如果你频繁打开文件,文件的handle 值会不断增加,等到超过整数的最大值,程序就无法打开文件