如何解决PHP无法实现多线程的问题


Posted in PHP onSeptember 25, 2015

有没有办法在PHP中实现多线程呢?假设你正在写一个基于多台服务器的PHP应用,理想的情况时同时向多台服务器发送请求,而不是一台接一台。可以实现吗?当有人想要实现并发功能时,他们通常会想到用fork或者spawn threads,但是当他们发现PHP不支持多线程的时候,大概会转换思路去用一些不够好的语言,比如Perl。
假设你要建立一个服务来检查正在运行的n台服务器,以确定他们还在正常运转。你可能会写下面这样的代码:

$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; 
$status = array(); 
foreach ($hosts as $host) {  
    $errno = 0;  
    $errstr = "";  
    $s = fsockopen($host, 80, $errno, $errstr, $timeout);  
    if ($s) {  
       $status[$host] = "Connectedn";  
       fwrite($s, "HEAD / HTTP/1.0rnHost: $hostrnrn");  
      do {   
        $data = fread($s, 8192);   
        if (strlen($data) == 0) {   
        break;   
        }   
       $status[$host] .= $data;  
     }  
     while (true);  
      fclose($s);  
     }  
     else {  
       $status[$host] = "Connection failed: $errno $errstrn";  
     } 
} 
print_r($status); 
?>

它运行的很好,但是在fsockopen()分析完hostname并且建立一个成功的连接(或者延时$timeout秒)之前,扩充这段代码来管理大量服务器将耗费很长时间。

因此我们必须放弃这段代码;我们可以建立异步连接-不需要等待fsockopen返回连接状态。PHP仍然需要解析hostname(所以直接使用ip更加明智),不过将在打开一个连接之后立刻返回,继而我们就可以连接下一台服务器。

有两种方法可以实现;PHP5中可以使用新增的stream_socket_client()函数直接替换掉fsocketopen()。PHP5之前的版本,你需要自己动手,用sockets扩展解决问题。下面是PHP5中的解决方法:

$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; 
$status = array(); 
$sockets = array(); 
/* Initiate connections to all the hosts simultaneously */ 
foreach ($hosts as $id => $host) {  
    $s = stream_socket_client("$host:80", $errno, $errstr, $timeout,  
       STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT);  
    if ($s) {  
      $sockets[$id] = $s;  
      $status[$id] = "in progress";  
    }  
    else { $status[$id] = "failed, $errno $errstr";  
    } 
} 
/* Now, wait for the results to come back in */ 
 
while (count($sockets)) {  
   $read = $write = $sockets;  
/* This is the magic function - explained below */  
   $n = stream_select($read, $write, $e = null, $timeout);  
   if ($n > 0) {  
   /* readable sockets either have data for us, or are failed  * connection attempts */  
     foreach ($read as $r) {    
          $id = array_search($r, $sockets);    
          $data = fread($r, 8192);    
     if (strlen($data) == 0) {   
          if ($status[$id] == "in progress") {   
            $status[$id] = "failed to connect";   
          }   
     fclose($r);   
     unset($sockets[$id]);    
      }  
      else {   
         $status[$id] .= $data;    
      }  
    }  
/* writeable sockets can accept an HTTP request */  
foreach ($write as $w) {   
     $id = array_search($w, $sockets);   
     fwrite($w, "HEAD / HTTP/1.0rnHost: "   
     . $hosts[$id] . "rnrn");   
     $status[$id] = "waiting for response";  
     }  
}  
else {  
/* timed out waiting; assume that all hosts associated  * with $sockets are faulty */  
foreach ($sockets as $id => $s) {   
     $status[$id] = "timed out "  
     . $status[$id];  
     }  
break;  
 } 
} 
foreach ($hosts as $id => $host) {  
   echo "Host: $hostn"; echo "Status: "  
   . $status[$id] . "nn"; 
}  
?>

我们用stream_select()等待sockets打开的连接事件。stream_select()调用系统的select(2)函数来工 作:前面三个参数是你要使用的streams的数组;你可以对其读取,写入和获取异常(分别针对三个参数)。stream_select()可以通过设 置$timeout(秒)参数来等待事件发生-事件发生时,相应的sockets数据将写入你传入的参数。

下面是PHP4.1.0之后版本的实现,如果你已经在编译PHP时包含了sockets(ext/sockets)支持,你可以使用根上面类似的代 码,只是需要将上面的streams/filesystem函数的功能用ext/sockets函数实现。主要的不同在于我们用下面的函数代替 stream_socket_client()来建立连接:

// This value is correct for Linux, other systems have other values 
define('EINPROGRESS', 115); 
function non_blocking_connect($host, $port, &$errno, &$errstr, $timeout) {  
    $ip = gethostbyname($host);  
    $s = socket_create(AF_INET, SOCK_STREAM, 0);  
    if (socket_set_nonblock($s)) {  
      $r = @socket_connect($s, $ip, $port);  
      if ($r || socket_last_error() == EINPROGRESS) {   
         $errno = EINPROGRESS;   
         return $s;  
        }  
     }  
    $errno = socket_last_error($s);  
    $errstr = socket_strerror($errno);  
    socket_close($s);  
    return false; 
} 
?>

现在用socket_select()替换掉stream_select(),用socket_read()替换掉fread(),用socket_write()替换掉fwrite(),用socket_close()替换掉fclose()就可以执行脚本了!
PHP5的先进之处在于,你可以用stream_select()处理几乎所有的stream。例如你可以通过include STDIN用它接收键盘输入并保存进数组,你还可以接收通过proc_open()打开的管道中的数据。

希望通过这篇文章,大家可以巧妙解决PHP无法实现多线程的问题。

PHP 相关文章推荐
PHP网页游戏学习之Xnova(ogame)源码解读(十四)
Jun 26 PHP
destoon供应信息title调用出公司名称的方法
Aug 22 PHP
ThinkPHP中关联查询实例
Dec 02 PHP
php实现字符串首字母大写和单词首字母大写的方法
Mar 14 PHP
php结合curl实现多线程抓取
Jul 09 PHP
php在数据库抽象层简单使用PDO的方法
Nov 03 PHP
PHP  实现等比压缩图片尺寸和大小实例代码
Oct 08 PHP
遍历echsop的region表形成缓存的程序实例代码
Nov 01 PHP
PHP 序列化和反序列化函数实例详解
Jul 18 PHP
使用PHP+MySql实现微信投票功能实例代码
Sep 29 PHP
PHP守护进程化在C和PHP环境下的实现
Nov 21 PHP
PHP设计模式之抽象工厂模式实例分析
Mar 25 PHP
PHP网站建设的流程与步骤分享
Sep 25 #PHP
ThinkPHP进程计数类Process用法实例详解
Sep 25 #PHP
php实现的Curl封装类Curl.class.php用法实例分析
Sep 25 #PHP
php实现的微信红包算法分析(非官方)
Sep 25 #PHP
PHP简单实现断点续传下载的方法
Sep 25 #PHP
分享3个php获取日历的函数
Sep 25 #PHP
PHP中配置IIS7实现基本身份验证的方法
Sep 24 #PHP
You might like
星际中一些鲜为人知的详细资料
2020/03/04 星际争霸
注册页面之前先验证用户名是否存在的php代码
2012/07/14 PHP
关于JSON以及JSON在PHP中的应用技巧
2013/11/27 PHP
php 模拟 asp.net webFrom 按钮提交事件实例
2014/10/13 PHP
PHP实现Session入库/存入redis的方法
2017/05/04 PHP
html的DOM中Event对象onabort事件用法实例
2015/01/21 Javascript
jQuery晃动层特效实现方法
2015/03/09 Javascript
javascript组合使用构造函数模式和原型模式实例
2015/06/04 Javascript
js简单判断移动端系统的方法
2016/02/25 Javascript
深入理解JavaScript中的对象复制(Object Clone)
2016/05/18 Javascript
JavaScript获取IP获取的是IPV6 如何校验
2016/06/12 Javascript
Angularjs的启动过程分析
2017/07/18 Javascript
基于jQuery实现手风琴菜单、层级菜单、置顶菜单、无缝滚动效果
2017/07/20 jQuery
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
javascript代码优化的8点总结
2018/01/29 Javascript
vue中锚点的三种方法
2018/07/06 Javascript
Vue2.0生命周期的理解
2018/08/20 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
一文快速了解JQuery中的AJAX
2019/05/31 jQuery
微信小程序使用GoEasy实现websocket实时通讯
2020/05/19 Javascript
小程序实现可拖动的悬浮按钮
2020/09/07 Javascript
一行JavaScript代码如何实现瀑布流布局
2020/12/11 Javascript
python中验证码连通域分割的方法详解
2018/06/04 Python
对pycharm代码整体左移和右移缩进快捷键的介绍
2018/07/16 Python
tensorflow实现简单逻辑回归
2018/09/07 Python
Python根据当前日期取去年同星期日期
2019/04/14 Python
Python常用模块logging——日志输出功能(示例代码)
2019/11/20 Python
详解python破解zip文件密码的方法
2020/01/13 Python
Pandas把dataframe或series转换成list的方法
2020/06/14 Python
威尔逊皮革:Wilsons Leather
2018/12/07 全球购物
师范院校学生自荐信范文
2013/12/27 职场文书
《玩具柜台前的孩子》教学反思
2014/02/13 职场文书
学校周年庆活动方案
2014/08/22 职场文书
会议主持词结束语
2015/07/03 职场文书
欢送领导祝酒词
2015/08/12 职场文书
总结一下关于在Java8中使用stream流踩过的一些坑
2021/06/24 Java/Android