這裏閱讀的php版本爲PHP-7.1.0 RC3,閱讀代碼的平臺爲linuxphp
咱們會看到文章中有不少地方是:html
#ifdef ZTS # define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v) #else # define CG(v) (compiler_globals.v) extern ZEND_API struct _zend_compiler_globals compiler_globals; #endif
這裏的ZTS是個什麼概念呢。咱們常常使用的php都是運行在單進程,單線程環境,好比cgi,都是一個請求進來,就一個進程爲它服務,當請求結束了,進程也就結束了。因此好比像全局變量,php內核就沒有考慮多線程同時修改獲取的時候線程安全問題。後來,php漸漸也在往單進程多線程服務器方向發展。那麼這個時候,就會須要有一個層來專門處理線程安全問題。這個就是TSRM(Thread Safe Resource Management)。linux
可是php默認是關閉線程安全的。在編譯的時候,你能夠指定參數開啓編譯一個線程安全版本的php。(--enable-maintainer-zts 選項, Windows 平臺爲 --enable-zts)這個就是這裏的ZTS的由來。安全
好比上面的例子,CG(V) 在非線程安全下獲取的是全局結構compiler_globals結構的v屬性,在線程安全下獲取的是經過ZEND_TSREMG方法來獲取。服務器
咱們會看到zend_try_catch相關的代碼以下:多線程
zend_try { ...exec_try } zend_catch { ...exec_catch } zend_end_try();
把宏展開,咱們能夠看到大概代碼以下:函數
{ \ JMP_BUF *__orig_bailout = EG(bailout); \ JMP_BUF __bailout; \ \ EG(bailout) = &__bailout; \ if (SETJMP(__bailout)==0) { { ...exec_try } } else { \ EG(bailout) = __orig_bailout; { ...exec_catch } } \ EG(bailout) = __orig_bailout; \ }
這個是什麼意思呢,須要先理解下setjmp和longjmp,這兩個函數是linux提供的方法。他們是組合起來使用的,達到協同程序的功能.net
#include <stdio.h> #include <setjmp.h> jmp_buf env; void foo() { printf("before jmp\n"); int ret = setjmp(env); if(ret == 0) { return; } else { printf("return %d\n", ret); } printf("after jmp\n"); } int main(int argc, char* argv[]) { foo(); longjmp(env, 999); return 0; } // 輸出: /* before jmp return 999 after jmp */
上面的這個例子,setjmp的時候至關於程序片斷1把主動權交出來,而後執行if(ret == 0)下面的程序,直到遇到longjmp,把執行權還給了片斷1,而且設置jmp_buf爲999,片斷1繼續執行,發現了ret!=0,就輸出return 999。線程
好了,回到這個程序:code
{ \ JMP_BUF *__orig_bailout = EG(bailout); \ JMP_BUF __bailout; \ \ EG(bailout) = &__bailout; \ if (SETJMP(__bailout)==0) { { ...exec_try } } else { \ EG(bailout) = __orig_bailout; { ...exec_catch } } \ EG(bailout) = __orig_bailout; \ }
這個程序裏面的exec_try代碼段裏面,在遇到錯誤的時候,須要返回的時候,就會包含一個longjmp函數的調用。這樣,就造成了咱們平時調用try...catch...finnal的功能:
1 先保存全局變量裏面的bailout
2 使用setjmp來作跳轉執行下面的程序
3 執行exec_try
4 若是exec_try這個代碼段裏面有longjmp,而且longjmp返回非0(通常也確實非0),就執行exec_catch
5 最後,把全局變量裏面的bailout恢復
這裏可能會有兩個疑惑,若是exec_try裏面沒有longjmp怎麼辦,那就直接只執行了exec_try,就跳過exec_catch了。這個也是標準的用setjmp和longjmp實現try catch的寫法。
這兩個的實現彌補了goto關鍵字只能在函數內部進行跳轉的限制。這個叫作「長跳轉」。
因此在PHP代碼中,若是你執行的函數有可能拋出異常。不妨使用這個方式把你要執行的程序放在裏面。
http://blog.lucode.net/skills/talk-about-setjmp-and-longjmp.html