PHP编程中尝试程序并发的几种方式总结


Posted in PHP onMarch 21, 2016

本文大约总结了PHP编程中的五种并发方式:
1.curl_multi_init
文档中说的是 Allows the processing of multiple cURL handles asynchronously. 确实是异步。这里需要理解的是select这个方法,文档中是这么解释的Blocks until there is activity on any of the curl_multi connections.。了解一下常见的异步模型就应该能理解,select, epoll,都很有名

<?php
// build the individual requests as above, but do not execute them
$ch_1 = curl_init('https://3water.com/');
$ch_2 = curl_init('https://3water.com/');
curl_setopt($ch_1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch_2, CURLOPT_RETURNTRANSFER, true);

// build the multi-curl handle, adding both $ch
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch_1);
curl_multi_add_handle($mh, $ch_2);

// execute all queries simultaneously, and continue when all are complete
$running = null;
do {
  curl_multi_exec($mh, $running);
  $ch = curl_multi_select($mh);
  if($ch !== 0){
    $info = curl_multi_info_read($mh);
    if($info){
      var_dump($info);
      $response_1 = curl_multi_getcontent($info['handle']);
      echo "$response_1 \n";
      break;
    }
  }
} while ($running > 0);

//close the handles
curl_multi_remove_handle($mh, $ch_1);
curl_multi_remove_handle($mh, $ch_2);
curl_multi_close($mh);

这里我设置的是,select得到结果,就退出循环,并且删除 curl resource, 从而达到取消http请求的目的。

2.swoole_client
swoole_client提供了异步模式,我竟然把这个忘了。这里的sleep方法需要swoole版本大于等于1.7.21, 我还没升到这个版本,所以直接exit也可以。

<?php
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
//设置事件回调函数
$client->on("connect", function($cli) {
  $req = "GET / HTTP/1.1\r\n
  Host: 3water.com\r\n
  Connection: keep-alive\r\n
  Cache-Control: no-cache\r\n
  Pragma: no-cache\r\n\r\n";

  for ($i=0; $i < 3; $i++) {
    $cli->send($req);
  }
});
$client->on("receive", function($cli, $data){
  echo "Received: ".$data."\n";
  exit(0);
  $cli->sleep(); // swoole >= 1.7.21
});
$client->on("error", function($cli){
  echo "Connect failed\n";
});
$client->on("close", function($cli){
  echo "Connection close\n";
});
//发起网络连接
$client->connect('183.207.95.145', 80, 1);

3.process
哎,竟然差点忘了 swoole_process, 这里就不用 pcntl 模块了。但是写完发现,这其实也不算是中断请求,而是哪个先到读哪个,忽视后面的返回值。

<?php

$workers = [];
$worker_num = 3;//创建的进程数
$finished = false;
$lock = new swoole_lock(SWOOLE_MUTEX);

for($i=0;$i<$worker_num ; $i++){
  $process = new swoole_process('process');
  //$process->useQueue();
  $pid = $process->start();
  $workers[$pid] = $process;
}

foreach($workers as $pid => $process){
  //子进程也会包含此事件
  swoole_event_add($process->pipe, function ($pipe) use($process, $lock, &$finished) {
    $lock->lock();
    if(!$finished){
      $finished = true;
      $data = $process->read();
      echo "RECV: " . $data.PHP_EOL;
    }
    $lock->unlock();
  });
}

function process(swoole_process $process){
  $response = 'http response';
  $process->write($response);
  echo $process->pid,"\t",$process->callback .PHP_EOL;
}

for($i = 0; $i < $worker_num; $i++) {
  $ret = swoole_process::wait();
  $pid = $ret['pid'];
  echo "Worker Exit, PID=".$pid.PHP_EOL;
}

4.pthreads
编译pthreads模块时,提示php编译时必须打开ZTS, 所以貌似必须 thread safe 版本才能使用. wamp中多php正好是TS的,直接下了个dll, 文档中的说明复制到对应目录,就在win下测试了。 还没完全理解,查到文章说 php 的 pthreads 和 POSIX pthreads是完全不一样的。代码有些烂,还需要多看看文档,体会一下。

<?php
class Foo extends Stackable {
  public $url;
  public $response = null;
  public function __construct(){
    $this->url = 'https://3water.com';
  }
  public function run(){}
}

class Process extends Worker {
  private $text = "";
  public function __construct($text,$object){
    $this->text = $text;
    $this->object = $object;
  }
  public function run(){
    while (is_null($this->object->response)){
      print " Thread {$this->text} is running\n";
      $this->object->response = 'http response';
      sleep(1);
    }
  }
}

$foo = new Foo();

$a = new Process("A",$foo);
$a->start();

$b = new Process("B",$foo);
$b->start();

echo $foo->response;

5.yield
以同步方式书写异步代码:

<?php 
 
class AsyncServer { 
  protected $handler; 
  protected $socket; 
  protected $tasks = []; 
  protected $timers = []; 
 
  public function __construct(callable $handler) { 
    $this->handler = $handler; 
 
    $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 
    if(!$this->socket) { 
      die(socket_strerror(socket_last_error())."\n"); 
    } 
    if (!socket_set_nonblock($this->socket)) { 
      die(socket_strerror(socket_last_error())."\n"); 
    } 
    if(!socket_bind($this->socket, "0.0.0.0", 1234)) { 
      die(socket_strerror(socket_last_error())."\n"); 
    } 
  } 
 
  public function Run() { 
    while (true) { 
      $now = microtime(true) * 1000; 
      foreach ($this->timers as $time => $sockets) { 
        if ($time > $now) break; 
        foreach ($sockets as $one) { 
          list($socket, $coroutine) = $this->tasks[$one]; 
          unset($this->tasks[$one]); 
          socket_close($socket); 
          $coroutine->throw(new Exception("Timeout")); 
        } 
        unset($this->timers[$time]); 
      } 
 
      $reads = array($this->socket); 
      foreach ($this->tasks as list($socket)) { 
        $reads[] = $socket; 
      } 
      $writes = NULL; 
      $excepts= NULL; 
      if (!socket_select($reads, $writes, $excepts, 0, 1000)) { 
        continue; 
      } 
 
      foreach ($reads as $one) { 
        $len = socket_recvfrom($one, $data, 65535, 0, $ip, $port); 
        if (!$len) { 
          //echo "socket_recvfrom fail.\n"; 
          continue; 
        } 
        if ($one == $this->socket) { 
          //echo "[Run]request recvfrom succ. data=$data ip=$ip port=$port\n"; 
          $handler = $this->handler; 
          $coroutine = $handler($one, $data, $len, $ip, $port); 
          if (!$coroutine) { 
            //echo "[Run]everything is done.\n"; 
            continue; 
          } 
          $task = $coroutine->current(); 
          //echo "[Run]AsyncTask recv. data=$task->data ip=$task->ip port=$task->port timeout=$task->timeout\n"; 
          $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 
          if(!$socket) { 
            //echo socket_strerror(socket_last_error())."\n"; 
            $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error())); 
            continue; 
          } 
          if (!socket_set_nonblock($socket)) { 
            //echo socket_strerror(socket_last_error())."\n"; 
            $coroutine->throw(new Exception(socket_strerror(socket_last_error()), socket_last_error())); 
            continue; 
          } 
          socket_sendto($socket, $task->data, $task->len, 0, $task->ip, $task->port); 
          $deadline = $now + $task->timeout; 
          $this->tasks[$socket] = [$socket, $coroutine, $deadline]; 
          $this->timers[$deadline][$socket] = $socket; 
        } else { 
          //echo "[Run]response recvfrom succ. data=$data ip=$ip port=$port\n"; 
          list($socket, $coroutine, $deadline) = $this->tasks[$one]; 
          unset($this->tasks[$one]); 
          unset($this->timers[$deadline][$one]); 
          socket_close($socket); 
          $coroutine->send(array($data, $len)); 
        } 
      } 
    } 
  } 
} 
 
class AsyncTask { 
  public $data; 
  public $len; 
  public $ip; 
  public $port; 
  public $timeout; 
 
  public function __construct($data, $len, $ip, $port, $timeout) { 
    $this->data = $data; 
    $this->len = $len; 
    $this->ip = $ip; 
    $this->port = $port; 
    $this->timeout = $timeout; 
  } 
} 
 
function AsyncSendRecv($req_buf, $req_len, $ip, $port, $timeout) { 
  return new AsyncTask($req_buf, $req_len, $ip, $port, $timeout); 
} 
 
function RequestHandler($socket, $req_buf, $req_len, $ip, $port) { 
  //echo "[RequestHandler] before yield AsyncTask. REQ=$req_buf\n"; 
  try { 
    list($rsp_buf, $rsp_len) = (yield AsyncSendRecv($req_buf, $req_len, "127.0.0.1", 2345, 3000)); 
  } catch (Exception $ex) { 
    $rsp_buf = $ex->getMessage(); 
    $rsp_len = strlen($rsp_buf); 
    //echo "[Exception]$rsp_buf\n"; 
  } 
  //echo "[RequestHandler] after yield AsyncTask. RSP=$rsp_buf\n"; 
  socket_sendto($socket, $rsp_buf, $rsp_len, 0, $ip, $port); 
} 
 
$server = new AsyncServer(RequestHandler); 
$server->Run(); 
 
?>

代码解读:

借助PHP内置array能力,实现简单的“超时管理”,以毫秒为精度作为时间分片;
封装AsyncSendRecv接口,调用形如yield AsyncSendRecv(),更加自然;
添加Exception作为错误处理机制,添加ret_code亦可,仅为展示之用。

PHP 相关文章推荐
PHP中文汉字验证码
Apr 08 PHP
php max_execution_time执行时间问题
Jul 17 PHP
PHP中英混合字符串截取函数代码
Jul 17 PHP
php 模拟GMAIL,HOTMAIL(MSN),YAHOO,163,126邮箱登录的详细介绍
Jun 18 PHP
destoon数据库表说明汇总
Jul 15 PHP
php CI框架插入一条或多条sql记录示例
Jul 29 PHP
php遍历目录方法小结
Mar 10 PHP
PHP导入导出Excel代码
Jul 07 PHP
PHP未登录自动跳转到登录页面
Dec 21 PHP
php实现XML和数组的相互转化功能示例
Feb 08 PHP
PHP递归遍历文件夹去除注释并压缩php源代码的方法示例
May 23 PHP
Yii框架常见缓存应用实例小结
Sep 09 PHP
PHP的Laravel框架中使用消息队列queue及异步队列的方法
Mar 21 #PHP
Zend Framework框架之Zend_Mail实现发送Email邮件验证功能及解决标题乱码的方法
Mar 21 #PHP
Zend Framework教程之Zend_Form组件实现表单提交并显示错误提示的方法
Mar 21 #PHP
Zend Framework实现多文件上传功能实例
Mar 21 #PHP
Zend Framework入门之环境配置及第一个Hello World示例(附demo源码下载)
Mar 21 #PHP
Zend Framework教程之连接数据库并执行增删查的方法(附demo源码下载)
Mar 21 #PHP
Zend Framework框架教程之Zend_Db_Table_Rowset用法实例分析
Mar 21 #PHP
You might like
phpfans留言版用到的install.php
2007/01/04 PHP
PHP配置文件中最常用四个ini函数
2007/03/19 PHP
在WAMP环境下搭建ZendDebugger php调试工具的方法
2011/07/18 PHP
PHP和JAVA中的重载(overload)和覆盖(override) 介绍
2012/03/01 PHP
Laravel中Trait的用法实例详解
2016/03/16 PHP
php解析base64数据生成图片的方法
2016/12/06 PHP
PHPCMS手机站伪静态设置详细教程
2017/02/06 PHP
PHP rsa加密解密算法原理解析
2020/12/09 PHP
广告显示判断
2006/08/31 Javascript
jquery随意添加移除html的实现代码
2011/06/21 Javascript
js汉字转拼音实现代码
2013/02/06 Javascript
js showModalDialog弹出窗口实例详解
2014/01/07 Javascript
jquery单行文字向上滚动效果的实现代码
2014/09/05 Javascript
js中setTimeout()与clearTimeout()用法实例浅析
2015/05/12 Javascript
jQuery数据类型小结(14个)
2016/01/08 Javascript
[原创]Javascript 实现广告后加载 可加载百度谷歌联盟广告
2016/05/11 Javascript
jQuery插件formValidator实现表单验证
2016/05/23 Javascript
vue中v-model动态生成的实例详解
2017/10/27 Javascript
微信小程序实现动态设置placeholder提示文字及按钮选中/取消状态的方法
2017/12/14 Javascript
Javascript中绑定click事件的四种方式介绍
2018/10/26 Javascript
node Buffer缓存区常见操作示例
2019/05/04 Javascript
layer.js open 隐藏滚动条的例子
2019/09/05 Javascript
Vue使用Three.js加载glTF模型的方法详解
2020/06/14 Javascript
解决vue使用vant下拉框van-dropdown-item 绑定title值不变问题
2020/08/05 Javascript
python处理PHP数组文本文件实例
2014/09/18 Python
Python字符串中添加、插入特定字符的方法
2019/09/10 Python
详解pandas中iloc, loc和ix的区别和联系
2020/03/09 Python
利用CSS3制作简单的3d半透明立方体图片展示
2017/03/25 HTML / CSS
澳大利亚运动鞋商店:Platypus Shoes
2019/09/27 全球购物
如何设置Java的运行环境
2013/04/05 面试题
广州御银科技股份有限公司试卷(C++)
2016/11/04 面试题
英语专业毕业生自我鉴定
2013/11/09 职场文书
认真学习保证书
2015/02/26 职场文书
2016年元旦致辞
2015/08/01 职场文书
老人院义工活动感想
2015/08/07 职场文书
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python