zoukankan      html  css  js  c++  java
  • php多进程编程相关资料(以备参考)

    进程与线程的区别

    
        要了解二者的区别与联系,首先得对进程与线程有一个宏观上的了解。
    
        进程,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。进程空间的大小 只与处理机的位数有关,一个 16 位长处理机的进程空间大小为 216 ,而 32 位处理机的进程空间大小为 232 。进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。
    
        线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。
    
        说到这里,我们对进程与线程都有了一个大体上的印象,现在开始说说二者大致的区别。
    
        进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。这是进程宏观上的执行过程。而进程又可有单线程进程与多线程进程两种。我们知道,进程有 一个进程控制块 PCB ,相关程序段 和 该程序段对其进行操作的数据结构集 这三部分,单线程进程的执行过程在宏观上是线性的,微观上也只有单一的执行过程;而多线程进程在宏观上的执行过程同样为线性的,但微观上却可以有多个执行操作(线程),如不同代码片段以及相关的数据结构集。线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。出了 CPU 之外,计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。与进程控制表和 PCB 相似,每个线程也有自己的线程控制表 TCB ,而这个 TCB 中所保存的线程状态信息则要比 PCB 表少得多,这些信息主要是相关指针用堆栈(系统栈和用户栈),寄存器中的状态数据。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
    
        线程可以有效地提高系统的执行效率,但并不是在所有计算机系统中都是适用的,如某些很少做进程调度和切换的实时系统。使用线程的好处是有多个任务需要处理机处理时,减少处理机的切换时间;而且,线程的创建和结束所需要的系统开销也比进程的创建和结束要小得多。最适用使用线程的系统是多处理机系统和网络系统或分布式系统。
    
    ----------------------------------
    
    1. 线程的执行特性。
    
        线程只有 3 个基本状态:就绪,执行,阻塞。
    
        线程存在 5 种基本操作来切换线程的状态:派生,阻塞,激活,调度,结束。
    
    2. 进程通信。
    
        单机系统中进程通信有 4 种形式:主从式,会话式,消息或邮箱机制,共享存储区方式。
    
            主从式典型例子:终端控制进程和终端进程。
    
            会话式典型例子:用户进程与磁盘管理进程之间的通信。

     

     

    一、PHP多进程任务调度实战(转:http://www.tuicool.com/articles/rEjuq2)

    由于PHP自身是单线程运行模型,所以通常在处理一些大数据量批量任务的时候会显得力不从心。不过在*nix环境下,可以通过php自带的pcntl模块实现*nix的进程创建,即可以在单个PHP脚本中fork多个子进程,从而提升并发执行效率。同样还能根据扩展,使用UNIX System V IPC机制,从而实现单机下多个子进程间根据信号量进行互斥执行,这在对数据库执行批量操作时极为有用。

    现在,我们可以通过PHP来实现一个类似*nix的Cron任务管理机制。在此需要安装pcntl模块以及sysvsem模块,具体的PHP扩展模块安装方法在此不再赘述,可以参考手册进行操作。

    为了便于任务的统一管理,可以将任务的路径和一些执行参数放在一个配置文件task_conf.php中统一管理,日后需要增删任务都只需修改这个文件即可。

    /**
     * 计划任务配置数组
     * 
     * @author imminh<minh.shu@gmail.com>
     * @since 2011-09-18
     * @example
     * $config['task_list'] = array(
     *     array(
     *         'path' => '/path/filename', //脚本路径
     *         'mutex' => true/false,      //是否互斥
     *         'time' => 60,               //任务执行间隔秒数s
     *         'child' => 2                //子进程数
     *     )
     *     ...
     * );
     */
    $config['task_list'] = array(
        array(
            'path'  => 'task1.php', 
            'mutex' => false, 
            'time'  => 2, 
            'child' => 3
        ),
        array(
            'path'  => 'task2.php', 
            'mutex' => true, 
            'time'  => 2, 
            'child' => 5
        )
    );

    然后在任务调度管理task_dispatch.php文件中,读取配置文件,并根据定义的脚本进行子进程的fork,判断是否为互斥任务,并在子进程中执行对应的任务执行器,这个脚本将作为全局执行的文件,可以放在后台执行,也可以放进crontab里,只要子任务脚本没有挂起,就不会产生重复的新任务。

    /**
     * 多进程同步任务
     * 
     * 服务器在*nix环境下,PHP安装pcntl,sysvsem模块
     * @author imminh<minh.shu@gmail.com>
     * @since 2011-09-18
     */
    
    set_time_limit(0);
    
    include_once 'task_conf.php';
    
    $child_task_process = array();
    if (is_array($config['task_list']) && !empty($config['task_list'])) {
        foreach ($config['task_list'] as $task) {
            $proc_exist = false;
            if (trim(exec('ps -ef|grep -E ' . $task['path'] . '|grep -v grep')) != '') {
                echo "进程 " . $task['path'] . " 已存在rn";
                $proc_exist = true;
                continue;
            }
    
            $child_pid = pcntl_fork();
            if ($child_pid) {
                $child_task_process[] = $child_pid;
            } else {
                break;
            }
        }
    
        if ($proc_exist == false) {
            if ($child_pid) {
                $status = null;
                foreach ($child_task_process as $pid) {
                    pcntl_waitpid($pid, $status, 0);
                }
            } else {
                //文件不存在跳出
                if (!file_exists($task['path']))
                    exit(1);
    
                /**
                 * 判断是否为互斥任务
                 */
                $cron_dir = realpath(dirname(__FILE__));
                if ($task['mutex']) {
                    $cron_script = $cron_dir . DIRECTORY_SEPARATOR . 'mutex_task.php';
                } else {
                    $cron_script = $cron_dir . DIRECTORY_SEPARATOR . 'sync_task.php';
                }
    
                $args = array($cron_script, $task['path'], $task['child'], $task['time']);
                pcntl_exec('/usr/bin/php', $args);
                exit ;
            }
        }
    }

    接下来先定义同步并行脚本的执行器sync_task.php,只是利用了pcntl模块fork新的子进程,并发性能取决于子进程数量,当然要在服务器负载承受范围内。

    /**
     * 单机多进程同步任务
     * 
     * 服务器在*nix环境下,PHP安装pcntl,sysvsem模块
     * @author imminh<minh.shu@gmail.com>
     * @since 2011-09-18
     */
    
    set_time_limit(0);
    
    /**
     * 使用方法
     * php-cgi方式调用,第一个参数为task脚本文件名,第二个参数为子进程数,第三个参数为间隔时长
     * 例如:php sync_task.php cron_send_mail.php 3 5
     */
    if ($argc > 1) {
        $filename = trim($argv[1]);
        $cron_dir = realpath(dirname(__FILE__));
        if (!file_exists( $cron_dir . DIRECTORY_SEPARATOR . $filename)) {
            exit('脚本文件名错误或不存在!');
        }
    } else {
        exit('参数错误!');
    }
    
    //最大子进程数
    $max_child = intval($argv[2]) > 0 ? intval($argv[2]) : 3;
    
    while (true) {
        //sleep N 秒
        sleep($argv[3]);
        
        //fork子进程
        $child_process = array();
        $child = 1;
        while ($child <= $max_child) {
         $child_pid = pcntl_fork();
         if ($child_pid == -1) {
             exit("子进程创建失败!n");
         } elseif ($child_pid) {
             $child_process[$child] = $child_pid;
             $child++;
         } else {
             break;
         }
        }
        
        if ($child_pid) {
         //父进程
         $status = null;
         foreach ($child_process as $pid) {
             pcntl_waitpid($pid, $status, 0);
         }
        } else {
         //子进程
         $msg = exec('/usr/bin/php '. $cron_dir . DIRECTORY_SEPARATOR . $filename);
                echo $msg."rn";
                exit;
        }
    }

    互斥任务执行器mutex_task.php相比而言,稍复杂一些,需要引入信号量机制,并在子进程处实现信号互斥,但是目前只是做到了进程间的数据互斥,而没有做到真正的数据操作互斥,和手动开启多个进程相比在执行效率上不会有太大差别,因此仍有改进空间。

    <?php
    /**
     * 单机多进程互斥任务
     *
     * 服务器在*nix环境下,PHP安装pcntl,sysvsem模块
     * @author imminh<minh.shu@gmail.com>
     * @since 2011-09-18
     */
    set_time_limit(0);
    /**
     * 使用方法
     * php-cgi方式调用,第一个参数为task脚本文件名,第二个参数为子进程数,第三个参数为间隔时长
     * @example:php mutex_task.php cron_send_mail.php 3 5
     */
    if ($argc > 1) {
        $filename = trim($argv[1]);
        $cron_dir = realpath(dirname(__FILE__));
        if (!file_exists( $cron_dir . DIRECTORY_SEPARATOR . $filename)) {
            exit('脚本文件名错误或不存在!');
        }
    } else {
        exit('参数错误!');
    }
    //最大子进程数
    $max_child = intval($argv[2]) > 0 ? intval($argv[2]) : 3;
    while (true) {
        //sleep N 秒
        sleep($argv[3]);
        //产生互斥信号量
        $key = ftok(__FILE__, 'a');
        $sem_id = sem_get($key, 1);
        //fork子进程
        $child_process = array();
        $child = 1;
        while ($child <= $max_child) {
            $child_pid = pcntl_fork();
            if ($child_pid == -1) {
                exit("子进程创建失败!
    ");
            } elseif ($child_pid) {
                $child_process[$child] = $child_pid;
                $child++;
            } else {
                break;
            }
        }
        if ($child_pid) {
            //父进程
            $status = null;
            foreach ($child_process as $pid) {
                pcntl_waitpid($pid, $status);
            }
        } else {
            //子进程
            sem_acquire($sem_id);
            $msg = exec('/usr/bin/php '. $cron_dir . DIRECTORY_SEPARATOR . $filename);
            echo $msg."
    ";
            sem_release($sem_id);
            exit ;
        }
    }

    切记在信号量互斥的时候,不要用pcntl_exec代替exec执行脚本,否则任务派发时将无法做到信号量互斥,而会在脚本执行完毕后关闭子进程。

    这样,只需在命令行执行以下命令,将任务放到后台执行,即可。

    1
    php task_dispatch .php &

    后记:本文利用php的pcntl,sysvsem扩展实现*nix环境下单机多进程以及信号量互斥,如需在多机上实现互斥,可以考虑使用memcache实现锁机制。

    https://github.com/imminh/code/tree/master/task

    二、PHP多进程编程实例

    (转:http://www.jb51.net/article/56301.htm

    羡慕火影忍者里鸣人的影分身么?没错,PHP程序是可以开动影分身的!想完成任务,又觉得一个进程太慢,那么,试试用多进程来搞吧。这篇文章将会介绍一下PHP多进程的基本需求,如何创建多进程以及基本的信号控制,暂时不会告诉你如何进行进程间通信和信息共享。

    1. 准备

    在动手之前,请确定你用的不是M$ Windows平台(因为我没有Windows)。Linux / BSD / Unix应该都是没问题的。确认好了工作环境以后一起来看看我们需要的PHP模块是否都有。打开终端输入下面的命令:

    复制代码 代码如下:

    $ php -m

    这个命令检查并打印当前PHP所有开启的扩展,看一下pcntl和posix是否在输出的列表中。

    1.1. pcntl

    如果找不到pcntl,八成是编译的时候没把这个扩展编译进去。如果你和我一样是编译安装的PHP,那么需要重新编译安装PHP。在配置的时候记得加上--enable-pcntl参数即可。

    复制代码 代码如下:

    $ cd /path/to/php_source_code_dir
    $ ./configure [some other options] --enable-pcntl
    $ make && make install

    1.2. posix

    这货一般默认就会装上,只要你编译的时候没有加上--disable-posix。

    2. 预备知识

    在继续之前,你还需要对Linux多进程有一点了解。多进程是咋回事呢?这里可跟火影忍者里的影分身稍微有点不同。首先,鸣人从小长到大,比如16岁,咳。有一天他发动了影分身,分出了5个他。显然,这些分身也是16岁的鸣人而不是刚出生啥也不懂就会哭的婴儿(那叫克隆)。然后,不一样的地方来了:分身们变成了独立的人各自去做各自的事,互相之间不再知道其他分身和原身都做了什么(当然不会像动画片里一样积累经验给原身啦)。除非,他们互相之间有交流,不然,只有16岁之前的事情才是他们共同的记忆。

    有同学说了,老大你这不坑爹呢么?我又没看过火影忍者!那你去看一遍好了……

    最后,预备知识完了,就是大致了解一下主进程开出来的子进程是怎么回事。子进程的代码和主进程是完全一样的,还有一部分一样的东西就是直到发动影分身之前执行的所有内容。

    3. 影分身之术

    所以呢,没有点基础知识怎么能理解卷轴里的内容呢?打开卷轴首先看到了一个单词:fork。

    3.1. fork

    叉子?叉子是分岔的,一个变多个嘛!差不多就是这个意思。创建子进程就用这个命令。这里需要用到pcntl_fork()函数。(可以先简单看一下PHP手册关于这个函数的介绍。)创建一个PHP脚本:

    复制代码 代码如下:

    $pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了
    if ($pid == -1) {
        die('fork failed');
    } else if ($pid == 0) {
    } else {
    }


    pcntl_fork()函数创建一个子进程,子进程和父进程唯一的区别就是PID(进程ID)和PPID(父进程ID)不同。在终端下查看进程用ps命令(问问man看ps怎么用:man ps)。当函数返回值为-1的时候,说明fork失败了。试试在if前面加一句:echo $pid . PHP_EOL;。运行你的脚本,输出可能像下面这样(结果说明子进程和父进程的代码是相同的):

    复制代码 代码如下:

    67789 # 这个是父进程打印的
    0     # 这个是子进程打印的


    pcntl_fork()函数调用成功后,在父进程中会返回子进程的PID,而在子进程中返回的是0。所以,下面直接用if分支来控制父进程和子进程做不同的事。

    3.2. 分配任务

    然后我们来说说鸣人16岁那次影分身的事儿,给原身和分身分配两个简单的输出任务:

    复制代码 代码如下:

    $parentPid = getmypid(); // 这就是传说中16岁之前的记忆
    $pid = pcntl_fork(); // 一旦调用成功,事情就变得有些不同了
    if ($pid == -1) {
        die('fork failed');
    } else if ($pid == 0) {
        $mypid = getmypid(); // 用getmypid()函数获取当前进程的PID
        echo 'I am child process. My PID is ' . $mypid . ' and my father's PID is ' . $parentPid . PHP_EOL;
    } else {
        echo 'Oh my god! I am a father now! My child's PID is ' . $pid . ' and mine is ' . $parentPid . PHP_EOL;
    }


    输出的结果可能是这样:

    复制代码 代码如下:

    Oh my god! I am a father now! My child's PID is 68066 and mine is 68065
    I am child process. My PID is 68066 and my father's PID is 68065


    再强调一下,pcntl_fork()调用成功以后,一个程序变成了两个程序:一个程序得到的$pid变量值是0,它是子进程;另一个程序得到的$pid的值大于0,这个值是子进程的PID,它是父进程。在下面的分支语句中,由于$pid值的不同,运行了不同的代码。再次强调一下:子进程的代码和父进程的是一样的。所以就要通过分支语句给他们分配不同的任务。

    3.3. 子进程回收

    刚刚有man ps么?一般我习惯用ps aux加上grep命令来查找运行着的后台进程。其中有一列STAT,标识了每个进程的运行状态。这里,我们关注状态Z:僵尸(Zombie)。当子进程比父进程先退出,而父进程没对其做任何处理的时候,子进程将会变成僵尸进程。Oops,又跟火影里的影分身不一样了。鸣人的影分身被干死了以后就自动消失了,但是这里的子进程分身死了话还留着一个空壳在,直到父进程回收它。僵尸进程虽然不占什么内存,但是很碍眼,院子里一堆躺着的僵尸怎么都觉得怪怪的。(别忘了它们还占用着PID)

    一般来说,在父进程结束之前回收挂掉的子进程就可以了。在pcntl扩展里面有一个pcntl_wait()函数,它会将父进程挂起,直到有一个子进程退出为止。如果有一个子进程变成了僵尸的话,它会立即返回。所有的子进程都要回收,所以多等等也没关系啦!

    3.4. 父进程先挂了

    如果父进程先挂了怎么办?会发生什么?什么也不会发生,子进程依旧还在运行。但是这个时候,子进程会被交给1号进程,1号进程成为了这些子进程的继父。1号进程会很好地处理这些进程的资源,当它们结束时1号进程会自动回收资源。所以,另一种处理僵尸进程的临时办法是关闭它们的父进程。

    4. 信号

    一般多进程的事儿讲到上面就完了,可是信号在系统中确实是一个非常重要的东西。信号就是信号灯,点亮一个信号灯,程序就会做出反应。这个你一定用过,比如说在终端下运行某个程序,等了半天也没什么反应,可能你会按 Ctrl+C 来关闭这个程序。实际上,这里就是通过键盘向程序发送了一个中断的信号:SIGINT。有时候进程失去响应了还会执行kill [PID]命令,未加任何其他参数的话,程序会接收到一个SIGTERM信号。程序收到上面两个信号的时候,默认都会结束执行,那么是否有可能改变这种默认行为呢?必须能啊!

    4.1. 注册信号

    人是活的程序也是活的,只不过程序需要遵循人制定的规则来运行。现在开始给信号重新设定规则,这里用到的函数是pcntl_signal()(继续之前为啥不先查查PHP手册呢?)。下面这段程序将给SIGINT重新定义行为,注意看好:

    复制代码 代码如下:

    // 定义一个处理器,接收到SIGINT信号后只输出一行信息
    function signalHandler($signal) {
        if ($signal == SIGINT) {
            echo 'signal received' . PHP_EOL;
        }
    }
    // 信号注册:当接收到SIGINT信号时,调用signalHandler()函数
    pcntl_signal(SIGINT, 'signalHandler');
    while (true) {
        sleep(1);
        // do something
        pcntl_signal_dispatch(); // 接收到信号时,调用注册的signalHandler()
    }


    执行一下,随时按下 Ctrl+C 看看会发生什么事。

    4.2. 信号分发

    说明一下:pcntl_signal()函数仅仅是注册信号和它的处理方法,真正接收到信号并调用其处理方法的是pcntl_signal_dispatch()函数。试试把// do something替换成下面这段代码:

    复制代码 代码如下:

    for ($i = 0; $i < 1000000; $i++) {
        echo $i . PHP_EOL;
        usleep(100000);
    }


    在终端下执行这个脚本,当它不停输出数字的时候尝试按下 Ctrl+C 。看看程序有什么响应?嗯……什么都没有,除了屏幕可能多了个^C以外,程序一直在不停地输出数字。因为程序一直没有执行到pcntl_signal_dispatch(),所以就并没有调用signalHandler(),所以就没有输出signal received。

    4.3. 版本问题

    如果认真看了PHP文档,会发现pcntl_signal_dispatch()这个函数是PHP 5.3以上才支持的,如果你的PHP版本大于5.3,建议使用这个方法调用信号处理器。5.3以下的版本需要在注册信号之前加一句:declare(ticks = 1);表示每执行一条低级指令,就检查一次信号,如果检测到注册的信号,就调用其信号处理器。想想就挺不爽的,干嘛一直都检查?还是在我们指定的地方检查一下就好。

    4.4. 感受僵尸进程

    现在我们回到子进程回收的问题上(差点忘了= =")。当你的一个子进程挂了(或者说是结束了),但是父进程还在运行中并且可能很长一段时间不会退出。一个僵尸进程从此站起来了!这时,保护伞公司(内核)发现它的地盘里出现了一个僵尸,这个僵尸是谁儿子呢?看一下PPID就知道了。然后,内核给PPID这个进程(也就是僵尸进程的父进程)发送一个信号:SIGCHLD。然后,你知道怎么在父进程中回收这个子进程了么?提示一下,用pcntl_wait()函数。

    4.5. 发送信号

    希望刚刚有认真man过kill命令。它其实就是向进程发送信号,在PHP中也可以调用posix_kill()函数来达到相同的效果。有了它就可以在父进程中控制其他子进程的运行了。比如在父进程结束之前关闭所有子进程,那么fork的时候在父进程记录所有子进程的PID,父进程结束之前依次给子进程发送结束信号即可。

    5. 实践

    PHP的多进程跟C还是挺像的,搞明白了以后用其他语言写的话也大同小异差不多都是这么个情况。如果有空的话,尝试写一个小程序,切身体会一下个中滋味:

    1.16岁的鸣人发送影分身,分出5个分身
    2.每个分身随机生存10到30秒,每秒都输出点什么
    3.保证原身能感受到分身的结束,然后开动另一个分身,保证最多有5个分身
    4.不使用nohup,让原身在终端关闭后依旧能够运行
    5.把分身数量(5)写进一个配置文件里,当给原身发送信号(可以考虑用SIGUSR1或SIGUSR2)时,原身读取配置文件并更新允许的分身最大数量
    6.如果分身多了,关闭几个;如果少了,再分出来几个

    提示:

    1.用while循环保证进程运行,注意sleep以免100%的CPU占用
    2.运行进程的终端被关闭时,程序会收到一个SIGHUP信号
    3.可以用parse_ini_file()函数解析INI配置文件

    三、 linux中fork()函数详解

    http://blog.csdn.net/cogbee/article/details/34885425

    四、php多进程几个例子

    http://www.phpfensi.com/php/20140617/3373.html

    php多进程这个东西先是在java中有不过现在高版本的php也支持多进程这个功能,但经过测试性能不如j(www.phpfensi.com)ava了希望后期有所提高了,下面我们一起来看看我整理了几个关于php多进程例子,希望能帮助你理解多线程.

    php多进程的实现依赖于pcntl扩展,编译PHP的时候,可以加上’–enable-pcntl’或者也可以单独编译.

    有三点需要注意:

    1.子进程不在执行fork之前的代码,只是把父进程的内存状况复制一份新的,所以,关于子进程的个性化设置需要单独设置.

    2.输出重定向,程序中使用echo,或造成命令行的混乱,影响分辨,可以用ob_start重定向到log文件,当然,你直接使用log是更好的办法,此实例中log文件,按照进程pid分组.

    3.父进程没有代码执行,将可能提前退出,子进程可能成为孤儿进程.

    demo接收:

    用10个子进程来处理输出任务,任务总量是1000,然后,按照任务数平均分到十个子进程当中去,代码如下:

     //输出重定向到log文件                    
    function echo_to_log($content){ 
        global $current_pid; 
        $logfile =  __FILE__ . $current_pid .  '.log'; 
        $fp = fopen($logfile, 'a+'); 
        fwrite($fp, $content); 
        fclose($fp); 
    } 
                                                                                      
    ob_start('echo_to_log'); 
    //获取当前进程pid 
    $current_pid = getmypid(); 
    $fork_nums = 10; 
    $total = 1000; 
                                                                                      
    for($i = 0; $i < $fork_nums; $i++){ 
        $pid = pcntl_fork(); 
        //等于0时,是子进程 
        if($pid == 0){ 
            $current_pid = $pid; 
            do_task($i); 
         //大于0时,是父进程,并且pid是产生的子进程的PID 
        } else if($pid > 0) { 
        } 
    } 
                                                                                      
    //任务函数 
    function do_task($task_num){ 
        global $total; 
        $start = $total / 10 * $task_num; 
        $end = $total / 10 * ($task_num + 1); 
        for(;$start<$end;$start++){ 
            echo $task_num . " " . $start . "\n"; 
        } 
        //子进程执行完任务以后终止,当然你可以返回主进程的代码部分做相关操作。 
        exit(); 
    } 

    多进程控制的框架代码,留着备查,代码如下:

    declare(ticks=1); 
    function sigHandler($signal) 
    { 
        echo "a child exited\n"; 
    } 
    pcntl_signal(SIGCHLD, sigHandler, false); 
    echo "this is " . posix_getpid() . PHP_EOL; 
    for($i=0; $i<3; $i++) 
    { 
        $pid = pcntl_fork(); 
        if($pid == -1)  
        {    
            echo 'fork failed ' . PHP_EOL; 
        }    
        else if($pid) 
        {    
        }    
        else 
        {    
            $pid = posix_getpid(); 
            echo 'child ' . $pid . ' ' . time() . PHP_EOL; 
            sleep(rand(2,5)); 
            echo 'child ' . $pid . ' done ' . time() . PHP_EOL; 
            exit(0); 
        }    
    } 
    do 
    { 
        $pid = pcntl_wait($status);  
        echo 'child quit ' . $pid . PHP_EOL; 
    }while($pid > 0);  
    echo 'parent done' . PHP_EOL; 

    例子,给出一段PHP多线程、与For循环,抓取百度搜索页面的PHP代码示例,代码如下:

    <?php 
      class test_thread_run extends Thread  
      { 
          public $url; 
          public $data; 
     
          public function __construct($url) 
          { 
              $this->url = $url; 
          } 
     
          public function run() 
          { 
              if(($url = $this->url)) 
              { 
                  $this->data = model_http_curl_get($url); 
              } 
          } 
      } 
     
      function model_thread_result_get($urls_array)  
      { 
          foreach ($urls_array as $key => $value)  
          { 
              $thread_array[$key] = new test_thread_run($value["url"]); 
              $thread_array[$key]->start(); 
          } 
     
          foreach ($thread_array as $thread_array_key => $thread_array_value)  
          { 
              while($thread_array[$thread_array_key]->isRunning()) 
              { 
                  usleep(10); 
              } 
              if($thread_array[$thread_array_key]->join()) 
              { 
                  $variable_data[$thread_array_key] = $thread_array[$thread_array_key]->data; 
              } 
          } 
          return $variable_data; 
      } 
     
      function model_http_curl_get($url,$userAgent="")  
      { 
          $userAgent = $userAgent ? $userAgent : 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2)';  
          $curl = curl_init(); 
          curl_setopt($curl, CURLOPT_URL, $url); 
          curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 
          curl_setopt($curl, CURLOPT_TIMEOUT, 5); 
          curl_setopt($curl, CURLOPT_USERAGENT, (www.phpfensi.com)$userAgent); 
          $result = curl_exec($curl); 
          curl_close($curl); 
          return $result; 
      } 
     
      for ($i=0; $i < 100; $i++)  
      {  
          $urls_array[] = array("name" => "baidu", "url" => "http://www.phpfensi.com/s?wd=".mt_rand(10000,20000)); 
      } 
     
      $t = microtime(true); 
      $result = model_thread_result_get($urls_array); 
      $e = microtime(true); 
      echo "多线程:".($e-$t)."\n"; 
     
      $t = microtime(true); 
      foreach ($urls_array as $key => $value)  
      { 
          $result_new[$key] = model_http_curl_get($value["url"]); 
      } 
      $e = microtime(true); 
      echo "For循环:".($e-$t)."\n"; 
    ?> 

    PHP多线程类,代码如下:

    /** 
     * @title:  PHP多线程类(Thread) 
     * @version: 1.0 
     * @author:   < web@ > 
     * @published: 2010-11-2 
     *  
     * PHP多线程应用示例: 
     *  require_once 'thread.class.php'; 
     *  $thread = new thread(); 
     *  $thread->addthread('action_log','a'); 
     *  $thread->addthread('action_log','b'); 
     *  $thread->addthread('action_log','c'); 
     *  $thread->runthread(); 
     *   
     *  function action_log($info) { 
     *   $log = 'log/' . microtime() . '.log'; 
     *   $txt = $info . "rnrn" . 'Set in ' . Date('h:i:s', time()) . (double)microtime() . "rn"; 
     *   $fp = fopen($log, 'w'); 
     *   fwrite($fp, $txt); 
     *   fclose($fp); 
     *  } 
     */ 
    class thread { 
     
        var $hooks = array(); 
        var $args = array(); 
         
        function thread() { 
        } 
         
        function addthread($func) 
        { 
         $args = array_slice(func_get_args(), 1); 
         $this->hooks[] = $func; 
      $this->args[] = $args; 
      return true; 
        } 
         
        function runthread() 
        { 
         if(isset($_GET['flag'])) 
         { 
          $flag = intval($_GET['flag']); 
         } 
         if($flag || $flag === 0) 
      { 
       call_user_func_array($this->hooks[$flag], $this->args[$flag]); 
      } 
         else  
         { 
             for($i = 0, $size = count($this->hooks); $i < $size; $i++) 
             { 
              $fp=fsockopen($_SERVER['HTTP_HOST'],$_SERVER['SERVER_PORT']); 
              if($fp) 
              { 
               $out = "GET {$_SERVER['PHP_SELF']}?flag=$i HTTP/1.1rn"; 
               $out .= "Host: {$_SERVER['HTTP_HOST']}rn"; 
               $out .= "Connection: Closernrn"; 
                     fputs($fp,$out); 
                     fclose($fp); 
              } 
             } 
         } 
        } 
    } 

    使用方法,代码如下:

    $thread = new thread(); 
    $thread->addthread('func1','info1'); 
    $thread->addthread('func2','info2'); 
    $thread->addthread('func3','info3'); 
    $thread->runthread(); 

    说明:addthread是添加线程函数,第一个参数是函数名,之后的参数(可选)为传递给指定函数的参数.

    runthread是执行线程的函数.

    在linux系统中需要配置安装一下pthreads

    1、扩展的编译安装(Linux),www.phpfensi.com 编辑参数 --enable-maintainer-zts 是必选项,代码如下:

    cd /Data/tgz/php-5.5.1 
    ./configure --prefix=/Data/apps/php --with-config-file-path=/Data/apps/php/etc --with-mysql=/Data/apps/mysql --with-mysqli=/Data/apps/mysql/bin/mysql_config --with-iconv-dir --with-freetype-dir=/Data/apps/libs --with-jpeg-dir=/Data/apps/libs --with-png-dir=/Data/apps/libs --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --enable-mbregex --enable-fpm --enable-mbstring --with-mcrypt=/Data/apps/libs --with-gd --enable-gd-native-ttf --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap --enable-opcache --with-pdo-mysql --enable-maintainer-zts 
    make clean 
    make 
    make install        
     
    unzip pthreads-master.zip 
    cd pthreads-master 
    /Data/apps/php/bin/phpize 
    ./configure --with-php-config=/Data/apps/php/bin/php-config 
    make 
    make install 
    vi /Data/apps/php/etc/php.ini 

    添加如下代码:

    extension = "pthreads.so" 

    PHP扩展下载:https://github.com/krakjoe/pthreads

    PHP手册文档:http://php.net/manual/zh/book.pthreads.php

  • 相关阅读:
    TCP/IP协议之ARP寻址
    流畅的python学习笔记:第九章:符合python风格的对象
    【原创】大叔经验分享(12)如何程序化kill提交到spark thrift上的sql
    【原创】大叔问题定位分享(16)spark写数据到hive外部表报错ClassCastException: org.apache.hadoop.hive.hbase.HiveHBaseTableOutputFormat cannot be cast to org.apache.hadoop.hive.ql.io.HiveOutputFormat
    【原创】大数据基础之Spark(3)Spark Thrift实现原理及代码实现
    【原创】大叔问题定位分享(15)spark写parquet数据报错ParquetEncodingException: empty fields are illegal, the field should be ommited completely instead
    【原创】大叔经验分享(11)python引入模块报错ImportError: No module named pandas numpy
    【原创】免费股票行情接口
    【原创】大叔经验分享(10)Could not transfer artifact org.apache.maven:maven. from/to central. Received fatal alert: protocol_version
    【原创】大叔经验分享(16)Context namespace element 'component-scan' and its parser class [org.springframework.context.annotation.ComponentScanBeanDefinitionParser] are only available on JDK 1.5 and higher
  • 原文地址:https://www.cnblogs.com/hubing/p/4936001.html
Copyright © 2011-2022 走看看