這裡閱讀的php版本為PHP-7.1.0 RC3,閱讀程式碼的平臺為linux
ZTS
我們會看到文章中有很多地方是:
1 2 3 4 5 6 |
#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)。
但是php預設是關閉執行緒安全的。在編譯的時候,你可以指定引數開啟編譯一個執行緒安全版本的php。(–enable-maintainer-zts 選項, Windows 平臺為 –enable-zts)這個就是這裡的ZTS的由來。
比如上面的例子,CG(V) 在非執行緒安全下獲取的是全域性結構compiler_globals結構的v屬性,線上程安全下獲取的是通過ZEND_TSREMG方法來獲取。
zend_try
我們會看到zend_try_catch相關的程式碼如下:
1 2 3 4 5 |
zend_try { ...exec_try } zend_catch { ...exec_catch } zend_end_try(); |
把巨集展開,我們可以看到大概程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ 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提供的方法。他們是組合起來使用的,達到協同程式的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include #include jmp_buf env; void foo() { printf("before jmpn"); int ret = setjmp(env); if(ret == 0) { return; } else { printf("return %dn", ret); } printf("after jmpn"); } 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。
好了,回到這個程式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ 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