Skip to content

Latest commit

 

History

History
93 lines (71 loc) · 5.01 KB

1.4.md

File metadata and controls

93 lines (71 loc) · 5.01 KB

1.4 线程安全

Native TLS(Native Thread local storage,原生线程本地存储)

PHP在多线程模式下(例如,Web服务器Apache的woker和event模式,就是多线程),需要解决“线程安全”(TS,Thread Safe)的问题,因为线程是共享进程的内存空间的,所以每个线程本身需要通过某种方式,构建私有的空间来保存自己的私有数据,避免和其他线程相互污染。而PHP5采用的方式,就是维护一个全局大数组,为每一个线程分配一份独立的存储空间,线程通过各自拥有的key值来访问这个全局数据组。 而这个独有的key值在PHP5中需要传递给每一个需要用到全局变量的函数,PHP7认为这种传递的方式并不友好,并且存在一些问题。因而,尝试采用一个全局的线程特定变量来保存这个key值。

不过这种方式(php5中使用的方式)不太好,她存在以下问题:

  • 1.许多函数不得不在TS模式下传递额外的参数(如TSRMLS_CC等),这使得我们在写代码时过多的使用TSRMLS_*宏。
  • 2.忘记传递TS参数在NTS(非线程安全模式下)不会带来任何问题,但在TS模式下会编译失败。
  • 3.另外,当我们在定义函数时如果在定义变量之前使用TSRMLS_FETCH()的话会造成C89的兼容性问题,这在Windows下进行扩展开发时更为明显。
  • 4.在TS模式时有些情况下需要进行条件编译(#ifdef ZTS)。
  • 5.当线程ID未明确的作为参数传递时,我们要使用TSRMLS_FETCH(),但这个宏的速度是很慢的。

传递线程的ID作为识别线程全局数据的手段这种方式在PHP7已经被移除了,换成了更合理的方式进行处理。 理想情况下我们使用一个全局的线程特定变量来做。

向后兼容

  • 1.线程ID不会再被明确的作为参数进行传递,许多函数的(声明)定义不需要再使用TSRM_*,但 TSRM_*宏依然保留.
  • 2.一些使用#ifdef ZTS宏进行条件编译的地方需要进行修改。

实施细节

PHP7是使用如下方式来实现的: 所有的(并非所有)TSRMLS_*宏被修改成了空的(比如TSRMLS_CC,TSRMLS_DC等)。 被用来访问全局数据的TSRMG宏可以通过以下两种方式进行替换:

  • 1.使用trsm_tls_cache()函数取得特定线程的全局变量,这个函数本身使用特定于操作系统相关的线程函数来取得全局变量(比如linux下的pthreads或者windows下的相关线程函数)。
  • 2.使用一个预定义的全局指针,然后每个线程更新这个指针来实现全局数据的访问与维护。
  • 3.php7使用上面的第二种方案

正如PHP的RFC中所写的那样,最主要困难是并不是每一个编译器(比如Visual Studio)都支持在共享对象间共享全局的线程特定变量。基于这个原因,函数必须至少调用tsrm_get_ls_cache()一次,以便使用线程相关的资源。这个操作需要在每个扩展的二进制版本中都要进行。

几乎每个全局访问宏(比如EG,CG等),都修改为使用特定于扩展的本地TSRMLS指针来访问全局变量。这需要三个步骤来做:

  • 1.为每一个扩展(如xx.so)定义一个指针变量来保存tsrmls的缓存即使用ZEND_TSRMLS_CACHE_DEFINE宏(展开后为TSRM_TLS void *TSRMLS_CACHE = NULL;)。
//extdev.c 扩展名为extdev
#ifdef COMPILE_DL_EXTDEV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(extdev)
#endif
  • 2.在头文件中对外暴露上面字义的 void *TSRMLS_CACHE 指针使用ZEND_TSRMLS_CACHE_EXTERN宏(展开后即为extern TSRM_TLS void *TSRMLS_CACHE;)。
//php_extdev.h
#if defined(ZTS) && defined(COMPILE_DL_EXTDEV)
ZEND_TSRMLS_CACHE_EXTERN()
#endif
  • 3.提供一个为每个线程更新void *TSRMLS_CACHE的机制,这里使用ZEND_TSRMLS_CACHE_UPDATE宏(展开后即是TSRMLS_CACHE_UPDATE() if (!TSRMLS_CACHE) TSRMLS_CACHE = tsrm_get_ls_cache())。
//extdev.c 每次请求中更新void *TSRMLS_CACHE指针
PHP_RINIT_FUNCTION(extdev)
{
#if defined(COMPILE_DL_EXTDEV) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}

头文件如下:

//zend.h
#define ZEND_TSRMLS_CACHE_EXTERN() TSRMLS_CACHE_EXTERN()
#define ZEND_TSRMLS_CACHE_DEFINE() TSRMLS_CACHE_DEFINE()
#define ZEND_TSRMLS_CACHE_UPDATE() TSRMLS_CACHE_UPDATE()

//TSRM.h
#define TSRMLS_CACHE_EXTERN() extern TSRM_TLS void *TSRMLS_CACHE;
#define TSRMLS_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE = NULL;
#if ZEND_DEBUG
#define TSRMLS_CACHE_UPDATE() TSRMLS_CACHE = tsrm_get_ls_cache()
#else
#define TSRMLS_CACHE_UPDATE() if (!TSRMLS_CACHE) TSRMLS_CACHE = tsrm_get_ls_cache()
#endif

在PHP7中我们使用ext_skel生成扩展时已经实现了这些。而且我们在以后的开发中也不需要再关心线程安全问题php7已经为我们做好了这一切。这在扩展开发上可以让我们节省不少代码,可以让我们更关注于业务的处理。

links