上一篇文章 workerman-todpole 执行流程(1),我们已经分析完了主进程的执行流程,这篇文章主要分析一下子进程的 run() 流程。

有必要提一下,在 run() 开始之前,其实针对角色对象的构造属性 $socket_name 已经开始了连接监听;下面开始分析 run() 方法:

run()

此时的 run() 方法虽然还属于 Worker 类的子类对象,但执行已经在子进程中,看一下代码:

public function run()
{
//Update process state.
static::$_status = static::STATUS_RUNNING;
 
// Register shutdown function for checking errors.
register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors'));
 
// Set autoload root path.
Autoloader::setRootPath($this->_autoloadRootPath);
 
// Create a global event loop.
if (!static::$globalEvent) {
$event_loop_class = static::getEventLoopName();
static::$globalEvent = new $event_loop_class;
$this->resumeAccept();
}
 
// Reinstall signal.
static::reinstallSignal();
 
// Init Timer.
Timer::init(static::$globalEvent);
 
// Set an empty onMessage callback.
if (empty($this->onMessage)) {
$this->onMessage = function () {};
}
 
// Try to emit onWorkerStart callback.
if ($this->onWorkerStart) {
try {
call_user_func($this->onWorkerStart, $this);
} catch (\Exception $e) {
static::log($e);
// Avoid rapid infinite loop exit.
sleep(1);
exit(250);
} catch (\Error $e) {
static::log($e);
// Avoid rapid infinite loop exit.
sleep(1);
exit(250);
}
}
 
// Main loop.
static::$globalEvent->loop();
}

首先将 Worker::$_status 标记为 STATUS_RUNNING,这段代码之前执行过一次,但那时还位于主进程空间内,所以这里我们可以理解为标记的是子进程的执行状态;紧接着注册错误处理方法来兜底,捕捉子进程出错的原因并记录日志。

getEventLoopName()

关于 Event Loop 这里不展开讲了,将来会单独发几篇文章,接着上面看一下如何创建 $globalEvent 对象,首先是 getEventLoopName() 代码:

protected static function getEventLoopName()
{
if (static::$eventLoopClass) {
return static::$eventLoopClass;
}
 
$loop_name = '';
foreach (static::$_availableEventLoops as $name=>$class) {
if (extension_loaded($name)) {
$loop_name = $name;
break;
}
}
 
if ($loop_name) {
if (interface_exists('\React\EventLoop\LoopInterface')) {
switch ($loop_name) {
case 'libevent':
static::$eventLoopClass = '\Workerman\Events\React\LibEventLoop';
break;
case 'event':
static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop';
break;
default :
static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop';
break;
}
} else {
static::$eventLoopClass = static::$_availableEventLoops[$loop_name];
}
} else {
static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select';
}
return static::$eventLoopClass;
}

整个方法就是为了找到一个合适的 $eventLoopClass(已经有的话直接返回),没有的话则找一个合适的,首先看一下 $_availableEventLoops 有哪些:

protected static $_availableEventLoops = array(
'libevent' => '\Workerman\Events\Libevent',
'event' => '\Workerman\Events\Event'
);

这两个类包括 Workerman\Events\Ev 和 Workerman\Events\Select 都实现自同一个接口:

除了 Select 之外,每种实现都基于已有的扩展:

libevent 属于老牌实现,libev 是在此基础上的精简和优化版本,由于 libev 不支持 windows,所以在此基础上又有了 libuv(nodejs 的 event-loop 即基于此库)。

回到 getEventLoopName(),首先判断 $_availableEventLoops 对应类的扩展是否安装,优先使用 libevent,如果都没有则使用本地实现 Workerman\Events\Select(死循环)。

除此之外还会判断是否引入了 ReactPHP(接口 \React\EventLoop\LoopInterface 是否存在),存在的话则使用 ReactPHP 相应的封装过的类。

拿到 $eventLoopClass 类之后,创建对象放入 $globalEvent 中,接下来的分析中,我们都以 Ev 实现为例。

reinstallSignal()

该方法将之前安装好的 signal handler 全部 ignore 掉,再通过刚刚创建的 $globalEvent 对象的 add()方法进行注册,和之前的主进程自己 dispatch 不同的是,子进程是通过 signal watcher 来监听信号事件的:

public function add($fd, $flag, $func, $args = null)
{
$callback = function ($event, $socket) use ($fd, $func) {
try {
call_user_func($func, $fd);
} catch (\Exception $e) {
Worker::log($e);
exit(250);
} catch (\Error $e) {
Worker::log($e);
exit(250);
}
};
switch ($flag) {
case self::EV_SIGNAL:
$event = new \EvSignal($fd, $callback);
$this->_eventSignal[$fd] = $event;
return true;
case self::EV_TIMER:
case self::EV_TIMER_ONCE:
$repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd;
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
$event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
$this->_eventTimer[self::$_timerId] = $event;
return self::$_timerId++;
default :
$fd_key = (int)$fd;
$real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
$event = new \EvIo($fd, $real_flag, $callback);
$this->_allEvents[$fd_key][$flag] = $event;
return true;
}
 
}

这里注册到 EvSignal 的 $callback 还是当初主进程使用的那个 signalHandler(),其实完全可以创建一个新的方法给子进程用的,我们翻代码发现信号处理调用的方法比如 stopAll()/reload() 会判断当前是处于主进程还是子进程,从而执行不同的操作,这也是为什么当初 fork 工作进程时要执行下面的操作:

static::$_workers = array($worker->workerId => $worker)

这样,主进程存放的是所有角色,而子进程只会存放自己所属的角色。

Timer::init()

这一步是将之前创建的 $globalEvent 对象放到定时器中(定时器每个子进程都有)。

回调

判断当前 onMessage() 回调是否设置,没有设置则默认给一个空的匿名闭包;另外 onWorkerStart() 回调正是在这一步被执行的。

loop()

终于到了最后一步,Ev 实现的 loop() 方法:

public function loop()
{
\Ev::run();
}

执行后子进程启动完成,默默的在后台等待 event-loop 给它触发。

至此,包括前一篇文章,主进程和子进程的启动流程已经分析完毕;但有个问题,我们仅仅是分析了 Worker 中的默认实现,并没有考虑到其实 BusinessWorker/Gateway/Register 已经重写了部分方法,因此,在接下来的文章中将依次介绍各个实现的执行流程。

workerman-todpole 执行流程(2)的更多相关文章

  1. workerman-todpole 执行流程(1)

    该系列文章主要是彻底扒一下 workerman todpole 游戏的实现原理. 提前打个预防针: 由于 Worker 类的静态属性和子类对象的混用(当前类的静态属性存放当前类对象,静态方法循环静态属 ...

  2. 步步深入:MySQL架构总览->查询执行流程->SQL解析顺序

    前言: 一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一篇博文了. 本文将从MySQL总体架构--->查询执行流程--->语句执行顺序来 ...

  3. 第二天 ci执行流程

    第二天 ci执行流程 welcome 页面 this this->load 单入口框架index.php 两个文件夹 system application定义 定义常亮路径 载入 codeign ...

  4. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  5. [Java编程思想-学习笔记]第4章 控制执行流程

    4.1  return 关键字return有两方面的用途:一方面指定一个方法结束时返回一个值:一方面强行在return位置结束整个方法,如下所示: char test(int score) { if ...

  6. ThinkPHP2.2框架执行流程图,ThinkPHP控制器的执行流程

    ThinkPHP2.2框架执行原理.流程图在线手册 ThinkPHP控制器的执行流程 对用户的第一次URL访问 http://<serverIp>/My/index.php/Index/s ...

  7. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

    最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, ...

  8. Servlet执行流程和生命周期【慕课网搬】

    Servlet执行流程(GET方式为例) 首先用户客户端浏览器发出Get方式(点击超链接方式)向浏览器发出请求. 服务器接收到客户端点击超链接,接收到GET请求之后,服务器到WEB.xml中<s ...

  9. javascript事件执行流程分析

    我一直想搞清楚事件在DOM中的传播方式,今天经高人指点终于明白一二.首先扒了一张图: 事件捕获过程:当我们点击TEXT时,首先是window->document->body->div ...

随机推荐

  1. 细看Thread的 start() 和 run()方法

    1.start(): 我们先来看看API中对于该方法的介绍: 使该线程开始执行:Java 虚拟机调用该线程的 run 方法. 结果是两个线程并发地运行:当前线程(从调用返回给 start 方法)和另一 ...

  2. PHP 获取url里文件的扩展名

    PHP 获取url地址中文件的扩展名 $url = 'http://www.abc.com/ab/cd/e.php?id=1&data=2#laowen';echo pathinfo( par ...

  3. vue中mounted中无法获取到dom元素

    一.解决方案: 加上异步setTimeout,延迟获取dom的代码的执行 mounted() { // debugger this.$nextTick(()=> { setTimeout(()= ...

  4. 2.C++语言特性

    一.普遍编程语言的特征 任何常用的编程语言都具备一组公共的语法特征,不同的语言仅在特征的细节上有所区别.所以,要想掌握一门语言,需要理解其语法特征的实现细节是第一步.  最基本的特征包括:       ...

  5. 一个简单的springmvc例子 入门(1)

    一直是从事棋牌游戏,平常用的东西 大多数只是使用一些javase的一些 api对spring 这方面 用到的比较少,每次学了都忘,始终记不住.为了 更轻松学习springboot,从新学习了sprin ...

  6. 用R语言实现对不平衡数据的四种处理方法

    https://www.weixin765.com/doc/gmlxlfqf.html 在对不平衡的分类数据集进行建模时,机器学**算法可能并不稳定,其预测结果甚至可能是有偏的,而预测精度此时也变得带 ...

  7. [转]Android 代码自动提示功能

    源地址http://blog.sina.com.cn/s/blog_7dbac12501019mbh.html 或者http://blog.csdn.net/longvslove/article/de ...

  8. 第一天课程:第一个python程序print say hello

    print("Hello World") linux下要加可执行权限 chmod 755 hello.py 权限755,7=4+2+1,4是读,2是写,1是执行,第一个7代表用户, ...

  9. Android中Handler.removeCallbacksAndMessages(null)的作用

    众所周知,在Android开发中会大量使用到Handler.但是,由于与activity生命周期不一致,当一个Activity被销毁时,如果Handler对象有Message在排队,则无法释放,进而导 ...

  10. echarts饼图配置

    js引用和配置div <div id="container" style="height: 100%"></div> <scrip ...