欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

php控制结构语句declare中的tick的详解[整理版]

程序员文章站 2022-05-08 20:52:55
...

先看看手册是怎么说的: declare 结构用来设定一段代码的执行指令。 declare 的语法和其它流程控制结构相: 1 declare (directive) 2 statement directive 部分允许设定 declare 代码段的行为。目前只认识两个指令: ticks (更多信息见下面ticks指令)以及 e

先看看手册是怎么说的:

declare 结构用来设定一段代码的执行指令。declare 的语法和其它流程控制结构相似:

1 declare (directive)

2 statement

directive 部分允许设定 declare 代码段的行为。目前只认识两个指令:ticks(更多信息见下面 ticks 指令)以及encoding(更多信息见下面 encoding指令)。

Note:

ticks 指令在 PHP 5.3.0 中是过时指令,将会从 PHP 6.0.0 移除。

encoding 是 PHP 5.3.0 新增指令。

Tick 是一个在 declare 代码段中解释器每执行 N 条低级语句就会发生的事件。N 的值是在 declare 中的 directive部分用 ticks=N 来指定的。

在每个 tick 中出现的事件是由 register_tick_function() 来指定的。更多细节见下面的例子。注意每个 tick 中可以出现多个事件。

看完手册还是觉得云里雾里,再看看别人是怎么描述:


根据代码解析:

01

02 function doTicks ()

03 {

04 echo 'Ticks';

05 }

06 register_tick_function('doTicks');

07 declare(ticks = 1) {

08 for ($x = 1; $x $x) {

09 echo $x * $x . '
'
;

10 }

11 }

12 ?>

运算结果:

01 1

02 TicksTicks4

03 TicksTicks9

04 TicksTicks16

05 TicksTicks25

06 TicksTicks36

07 TicksTicks49

08 TicksTicks64

09 TicksTicks81

10 TicksTicksTicksTicks

产生三个疑问:


(1)为什么先输出1之后才输出“Ticks”?

(2)为什么在输出81后还输出四个Ticks ?

(3)declare中的for循环怎么分解成低级语句(low-level)?

这是每个初次接触ticks的人都会碰到的问题。首先register_tick_function函数定义了每个tick事件发生时的处理函数。那么什么是tick事件呢?先看手册上对ticks的解释:

1 A tick is an event that occurs for every N low-level statements executed by the parser within the declare block. The value for N is specified using ticks=N within the declare blocks's directive div.

2 The event(s) that occur on each tick are specified using the register_tick_function().

这个解释有三层意思:

(1) tick是一个事件。

(2) tick事件在PHP每执行N条低级语句就发生一次,N由declare语句指定。

(3)可以用register_tick_function()来指定tick事件发生时应该执行的操作。

很明显,理解上面的输出结果最关键的是了解什么是低级语句(low-level statements),它又是如何进行计数的。我们首先还是将上面的程序通过OPDUMP编译成OPCODEs:

01 1:

02 0 NOP

03 2:

04 3: function doTicks ()

05 4: {

06 5: echo 'Ticks';

07 0 ECHO 'Ticks'

08 6: }

09 1 RETURN null

10 7: register_tick_function('doTicks');

11 1 SEND_VAL 'doTicks'

12 2 DO_FCALL 'register_tick_function' [extval:1]

13 8: declare(ticks = 1) {

14 9: for ($x = 1; $x $x) {

15 3 ASSIGN !0, 1

16 4 IS_SMALLER !0, 10 =>RES[~2]

17 5 JMPZNZ ~2, ->14 [extval:8]

18 6 PRE_INC !0

19 7 JMP ->4

20 10: echo $x * $x . '
'
;

21 8 MUL !0, !0 =>RES[~4]

22 9 CONCAT ~4, '
'
=>RES[~5]

23 10 ECHO ~5

24 11 TICKS 1 =>RES[]

25 11: }

26 12 TICKS 1 =>RES[]

27 13 JMP ->6

28 14 TICKS 1 =>RES[]

29 12: }

30 15 TICKS 1 =>RES[]

31 16 RETURN 1

很明显,PHP的编译过程已经在编译后每条语句的OPCODE序列中插入了TICKS指令用于处理tick事件。那么这些TICKS是根据什么规则来插入的呢?

我们还是从PHP Zend Engine的源代码中寻找答案。

通过简单的文本搜索我们可以知道生成ZEND_TICKS指令的唯一函数是zend_do_ticks(该函数的实现在zend_compile.c中)。

现在再从PHP的语法分析文件zend_language_parser.y(PHP使用bison来做语法分析,所有的语法规则均定义在zend_language_parser.y中)中寻找调用zend_do_ticks的地方。

再一次使用简单的文本搜索,我们可以得到调用zend_do_ticks的三条语法规则:

01 statement:

02 unticked_statement { zend_do_ticks(TSRMLS_C); }

03 | ...

04 ;

05 function_declaration_statement:

06 unticked_function_declaration_statement { zend_do_ticks(TSRMLS_C); }

07 ;

08 class_declaration_statement:

09 unticked_class_declaration_statement { zend_do_ticks(TSRMLS_C); }

10 ;

也就是说,PHP编译会在statement(语句), function_declaration_statement(函数定义语句), class_declaration_statement后插入TICKS处理函数,即它会在每条statement,函数声明,类(实际上还包括接口)声明后插入一条TICKS指令。

函数与类声明语句比较好理解,根据unticked_function_declaration_statement的语法定义:

1 unticked_function_declaration_statement:

2 function is_reference T_STRING { zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }

3 '(' parameter_list ')' '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1TSRMLS_CC); }

4 ;
可以知道function_declaration_statement是完整的函数声明,也就是说,一个函数的定义包括完整的函数原形及函数体算一条function_declaration_statement。

同样从unticked_class_declaration_statement的语法定义:

01 unticked_class_declaration_statement:

02 class_entry_type T_STRING extends_from

03 { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }

04 implements_list

05 '{'

06 class_statement_list

07 '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }

08 | interface_entry T_STRING

09 { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); }

10 interface_extends_list

11 '{'

12 class_statement_list

13 '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }

14 ;
也可以知道,完整的class或interface定义算是一个class_declration_statement。

最复杂的是statement,它的核心是下面的定义:

01 unticked_statement:

02 '{' inner_statement_list '}'

03 | T_IF '(' expr ')' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } statement { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); }

04 | T_IF '(' expr ')' ':' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } inner_statement_list { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } new_elseif_list new_else_single T_ENDIF ';' { zend_do_if_end(TSRMLS_C); }

05 | T_WHILE '(' { $1.u.opline_num = get_next_op_number(CG(active_op_array)); } expr ')' { zend_do_while_cond(&$4, &$5 TSRMLS_CC); } while_statement { zend_do_while_end(&$1, &$5TSRMLS_CC); }

06 | T_DO { $1.u.opline_num = get_next_op_number(CG(active_op_array)); zend_do_do_while_begin(TSRMLS_C); } statement T_WHILE '(' { $5.u.opline_num = get_next_op_number(CG(active_op_array)); } expr ')' ';' { zend_do_do_while_end(&$1, &$5, &$7TSRMLS_CC); }

07 | T_FOR

08 '('

09 for_expr

10 ';' { zend_do_free(&$3 TSRMLS_CC); $4.u.opline_num = get_next_op_number(CG(active_op_array)); }

11 for_expr

12 ';' { zend_do_extended_info(TSRMLS_C); zend_do_for_cond(&$6, &$7 TSRMLS_CC); }

13 for_expr

14 ')' { zend_do_free(&$9 TSRMLS_CC); zend_do_for_before_statement(&$4, &$7 TSRMLS_CC); }

15 for_statement { zend_do_for_end(&$7 TSRMLS_CC); }

16 | T_SWITCH '(' expr ')' { zend_do_switch_cond(&$3 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$6 TSRMLS_CC); }

17 | T_BREAK ';' { zend_do_brk_cont(ZEND_BRK, NULL TSRMLS_CC); }

18 | T_BREAK expr ';' { zend_do_brk_cont(ZEND_BRK, &$2 TSRMLS_CC); }

19 | T_CONTINUE ';' { zend_do_brk_cont(ZEND_CONT, NULL TSRMLS_CC); }

20 | T_CONTINUE expr ';' { zend_do_brk_cont(ZEND_CONT, &$2 TSRMLS_CC); }

21 | T_RETURN ';' { zend_do_return(NULL, 0 TSRMLS_CC); }

22 | T_RETURN expr_without_variable ';' { zend_do_return(&$2, 0 TSRMLS_CC); }

23 | T_RETURN variable ';' { zend_do_return(&$2, 1 TSRMLS_CC); }

24 | T_GLOBAL global_var_list ';'

25 | T_STATIC static_var_list ';'

26 | T_ECHO echo_expr_list ';'

27 | T_INLINE_HTML { zend_do_echo(&$1 TSRMLS_CC); }

28 | expr ';' { zend_do_free(&$1 TSRMLS_CC); }

29 | T_UNSET '(' unset_variables ')' ';'

30 | T_FOREACH '(' variable T_AS

31 { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); }

32 foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7TSRMLS_CC); }

33 foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }

34 | T_FOREACH '(' expr_without_variable T_AS

35 { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); }

36 variable foreach_optional_arg ')' { zend_check_writable_variable(&$6); zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }

37 foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }

38 | T_DECLARE { $1.u.opline_num = get_next_op_number(CG(active_op_array)); zend_do_declare_begin(TSRMLS_C); } '(' declare_list ')' declare_statement { zend_do_declare_end(&$1 TSRMLS_CC); }

39 | ';' /* empty statement */

40 | T_TRY { zend_do_try(&$1 TSRMLS_CC); } '{' inner_statement_list '}'

41 T_CATCH '(' { zend_initialize_try_catch_element(&$1 TSRMLS_CC); }

42 fully_qualified_class_name { zend_do_first_catch(&$7 TSRMLS_CC); }

43 T_VARIABLE ')' { zend_do_begin_catch(&$1, &$9, &$11, &$7 TSRMLS_CC); }

44 '{' inner_statement_list '}' { zend_do_end_catch(&$1 TSRMLS_CC); }

45 additional_catches { zend_do_mark_last_catch(&$7, &$18 TSRMLS_CC); }

46 | T_THROW expr ';' { zend_do_throw(&$2 TSRMLS_CC); }

47 | T_GOTO T_STRING ';' { zend_do_goto(&$2 TSRMLS_CC); }

48 ;

根据上面的定义,我们知道,statement包括:

(1) 简单语句:空语句(就一个;号),return,break,continue,throw, goto,global,static,unset,echo, 内置的HTML文本,分号结束的表达式等均算一个语句。

(2) 复合语句:完整的if/elseif,while,do...while,for,foreach,switch,try...catch等算一个语句。

(3) 语句块:{} 括出来的语句块。

(4) 最后特别的:declare块本身也算一个语句(按道理declare块也算是复合语句,但此处特意将其独立出来)。

所有的statement, function_declare_statement, class_declare_statement就构成了所谓的低级语句(low-level statement)。


现在再来看开始的例子就比较好理解了:

首先完整的for循环算一个语句,但必须要等循环结束才算,因此在编译时for循环里面的echo 算第一个语句。

所以第一个doTicks是在第一个echo后执行的,也就是1输出后才发生第一个tick事件。

在$x 从1到9的循环中,每个循环包括两个语句,一个echo, 一个for循环。在81输出后,因为echo是一条语句,因此输出第一个ticks。

同时$x=9的这个for循环也结束了,这又是一条语句,输出第二个ticks;开始$x=10的循环,但这时已不满足循环条件,for循环执行结束,这个循环又是一个语句,这时输出第三个ticks。

最后declare本身也算一条语句,所以又输出第四个ticks。


说了半天,ticks到底有什么用?实际上可用tick来进行调试,性能测试,实现简单的多任务,或者做一些后台的I/O操作等等。