TSRM到底是什么?

rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cxinchen.hui%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5Cxinchen.hui%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5Cxinchen.hui%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml"> · 作者:laruence( http://www.laruence.com/)
· 本文地址: http://www.laruence.com/2008/08/03/201.html
· 转载请注明出处

  如果你曾经做过PHP的扩展,或者研究过PHP的源码,你就会看到这个东西到处都在。但是关于这个东西是什么,却鲜有资料叙及。

             对于这个东西是什么,最常见的回答就是“你不用关心这个是什么,你只要在‘这里’‘那里’用上就是了,如果编译器告诉你缺少tsrm_ls,加上就好了”。这个答案虽然是一种很敷衍的回答,但其实也是有一定道理的,因为Zend Engine把这个宏搞的太复杂,并且对于一个初学PHP扩展的开发者来说,了解它是什么也没有太大的益处。

    而我是一个喜欢追根究底的人。所以,如果你现在刚好比较闲,并有耐性了解这个东西是什么,那么就请继续读下去。

    名词解释:

TSRM

         线程安全资源管理器(Thread Safe Resource Manager),这是个尝尝被忽视,并很少被人说起的“层”(layer), 她在PHP源码的/TSRM目录下。一般的情况下,这个层只会在被指明需要的时候才会被启用(比如,Apache2+worker MPM,一个基于线程的MPM),对于Win32下的Apache来说,是基于多线程的,所以这个层在Win32下总是被启用的。

ZTS

Zend线程安全(Zend Thread Safety),当TSRM被启用的时候,就会定义这个名为ZTS的宏。

tsrm_ls

TSRM存储器(TSRM Local Storage),这个是在扩展和Zend中真正被实际使用的指代TSRM存储的变量名。

TSRMLS_??

   这是一族(4)宏,用来根据ZTS宏被定义与否来实现TSRM4个宏如下:

                   #define TSRMLS_C   tsrm_ls

        #define TSRMLS_D   void  *** tsrm_ls

        #define TSRMLS_CC  ,tsrm_ls

        #define TSRMLS_DS  ,void  ***tsrm_ls   //注意有个逗号

 

   我们都知道,在C或者PHP编程中,要在多个函数中访问同一个变量有俩种方式,一种是通过参数传递,比如下面的代码:

    #include <stdio.h>

 

    void output_func(char *message)

    {

        printf("%s/n", message);

    }

 

    int main(int argc, char *argv[])

    {

        output_func(argv[0]);

 

        return 0;

    }

 

  另外一种方式是,通过在函数的高一级作用域中存储这个变量(当然,对于PHP,要显示的指明Global变量(这个原因和PHP的作用域的实现-活动表有关系,本处不涉及,我会在将来的某篇文章中介绍她),如:

    #include <stdio.h>

 

    char *message;

 

    void output_func(void)

    {

        printf("%s/n", message);

    }

 

    int main(int argv, char *argv[])

    {

        message = argv[0];

        output_func();

 

        return 0;

}

对于在PHP使用第二种方式来说,一般的单线程模型比如PHP CLI方式,Apache1,或者Apache2+prefork MPM(也是一种多进程模型),可以放心的被使用,也不会出错。全局变量在MINIT/RINIT的时候被创建,然后在整个进程运行时/请求处理期都能被访问到,然后在MSHUTDOW/RSHUTDOWN的时候被释放。

         但是在多线程的模型下,这种方式就不在安全了,比如Apache2+worker MPMIIS。在这种情况下,所有的线程共享同一个进程的地址空间,也就说,多个线程共用一个全局变量,这个时候就会产生竞争。用C程序员的方式来说:这个时候的全局变量是非线程安全的。

   为了解决这个问题,并和单线程模式兼容,Zend使用了称作“Non_global Globals”的机制。这个机制的主要思想就是,对于多线程模型来说,每当一个新的线程被创建,就单独的分配一块内存,这块内存存储着一个全局变量的副本。而这块内存会被一个Vector串起来,由Zend统一管理。为了说明这个方式,咱们看看如下的例子:

 

    typedef struct _zend_myextension_globals {

        int foo;

        char *bar;

    } zend_myextension_globals;

 

    #ifdef ZTS  //如果TSRM被启用

    int myextension_globals_id;

    #else

    zend_myextension_globals myextension_globals;

    #endif

 

    /* 当线程被创建的时候调用 */

    static void php_myextension_globals_ctor(zend_myextension_globals *myext_globals TSRMLS_DC)

    {

        myext_globals->foo = 0;

        myext_globals->bar = NULL;

    }

 

    /* 线程结束的时候被调用 */

    static void php_myextension_globals_dtor(zend_myextension_globals *myext_globals TSRMLS_DC)

    {

        if (myext_globals->bar) {

            efree(myext_globals->bar);

        }

    }

 

    PHP_MINIT_FUNCTION(myextension)

    {

    #ifdef ZTS

        ts_allocate_id(&myextension_globals_id, sizeof(zend_myextension_globals),

                       php_myextension_globals_ctor, php_myextension_globals_dtor);

    #else

        php_myextension_globals_ctor(&myextension_globals TSRMLS_CC);

    #endif

 

        return SUCCESS;

    }

 

    PHP_MSHUTDOWN_FUNCTION(myextension)

    {

    #ifndef ZTS

        php_myextension_globals_dtor(&myextension_globals TSRMLS_CC);

    #endif

 

        return SUCCESS;

    }

  这个例子开始的时候向TSRM层申明了一个全局变量” zend_myextension_globals”

        ts_allocate_id(&myextension_globals_id, sizeof(zend_myextension_globals),

                       php_myextension_globals_ctor, php_myextension_globals_dtor);

 他指明了要申请的全局变量的大小,创建器和析构器。并讲这个生成的全局变量在Vector中的偏移量(Index)保存在了myextension_globals_id中。而对于没有启用TSRM的情况,这个全局变量只是简单的被创建。

 如果你问我“为什么在没有启用TSRM的情况下还会有TSRMLS_CC?,那说明你现在还没有被我弄糊涂;),恩,在ZTS没有被设置的情况下(没有启用TSRM)TSRMLS_CC会被编译器替换为空,因为:

     #ifdef ZTS

              #define TRSMLS_CC  ,tsrm_ls

  #else

      #define TSRMLS_CC

  #endif

   在没有启用TSRM的情况下还指明TSRMLS_CC的原因仅仅是为了保持代码的一致性。


   恩,现在已经设置了全局变量,那么接下来的问题就是,我们如果去访问它呢?看看如下的代码:

 

    #ifdef ZTS

    # define   MYEXTENSION_G(v)     /

                 (((zend_myextension_globals*)(*((void ***)tsrm_ls))[(myextension_globals_id)-1])->v)

    #else

    # define   MYEXTENSION_G(v)     (myextension_globals.v)

    #endif

 

呵呵,明白了吧? ZTS没有被设置的情况下,宏MYEXTENSION_G(V)简单的被等价于全局变量myextension_globals.v,而对于启用了TSRM的情况,MYEXTENSION_G(V)会被转化成在Vector中根据my_extension_globals_id来查找到要访问的全局变量。

现在,只要你在你的代码中,使用MYEXTENSION_G来访问你的全局变量,并在要使用这个全局变量的函数参数列表中添加上TSRMLS_CC,那么就能保证在单线程和多线程模型下的线程安全,和代码一致性。:)

  

   最后,如果有问题,欢迎来信交流。;)

 

 特别鸣谢Sara Golemon大师对本文的贡献

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值