PHP ソース コードを表示するとき、または PHP 拡張機能を開発するときに、関数パラメーターの位置に多数の TSRMLS_ マクロが表示されます。これらのマクロは、スレッドを保証するためのスレッド セーフティ メカニズム (Zend Thread `Safety、略して ZTS) のために Zend によって提供されます。セキュリティは、PHP インタープリターがマルチスレッド環境でモジュールの形式でロードおよび実行され、一部の内部パブリック リソースで読み取りエラーが発生するのを防ぐために提供されるソリューションです。
いつTSRMを使用する必要がありますか
サーバーがマルチスレッド環境であり、PHPがモジュールの形式で提供されている限り、ワーカーモード(マルチプロセス)などのTSRMを有効にする必要がありますApache では、この状況が必要です。PHP のスレッドセーフ バージョンを使用するには、Linux では、PHP をコンパイルするときに TSRM を有効にするかどうかを指定します。スレッドセーフではないバージョンの PHP が提供されます。
PHP で TSRM を実装する方法
通常のマルチスレッド環境では、パブリック リソースを操作するためにミューテックス ロックが追加されますが、ロックによってパフォーマンスが低下する可能性があるため、PHP の解決策は各スレッドを提供することです。現在の PHP カーネルのすべてのパブリック リソースのコピーをコピーします。各スレッドは独自のパブリック リソース領域を指し、相互に影響を与えません。
パブリックリソースとは
さまざまな構造体定義です
TSRMデータ構造
tsrm_tls_entryスレッド構造、各スレッドにはこの構造のコピーがあります
typedef struct _tsrm_tls_entry tsrm_tls_entry; struct _tsrm_tls_entry { void **storage; int count; THREAD_T thread_id; tsrm_tls_entry *next; } static tsrm_tls_entry **tsrm_tls_table = NULL //线程指针表头指针 static int tsrm_tls_table_size; //当前线程结构体数量
フィールドの説明
void **storage :资源指针、就是指向自己的公共资源内存区 int count : 资源数、就是 PHP内核 + 扩展模块 共注册了多少公共资源 THREAD_T thread_id : 线程id tsrm_tls_entry *next:指向下一个线程指针,因为当前每一个线程指针都存在一个线程指针表里(类似于hash表),这个next可以理解成是hash冲突链式解决法. tsrm_resource_type 公共资源类型结构体、注册了多少公共资源就有多少个该结构体
フィールドの説明
typedef struct { size_t size; ts_allocate_ctor ctor; ts_allocate_dtor dtor; int done; } tsrm_resource_type; static tsrm_resource_type *resource_types_table=NULL; //公共资源类型表头指针 static int resource_types_table_size; //当前公共资源类型数量
グローバルリソースID
rrreええグローバルリソースIDとは
TSRMはパブリックリソースを登録する際にリソースごとに一意のIDを生成します。今後リソースを取得する際には、対応するリソースIDを指定する必要があります。
なぜグローバル リソース ID が必要なのか
各スレッドは現在登録されているすべてのパブリック リソース (malloc() の大きな配列) をコピーするためです。このリソース ID は配列のインデックス、つまり取得するものです。対応するリソースを指定するには、対応するリソースの ID を指定する必要があります。
理解するのは簡単です:
TSRM では、各スレッドが独自のパブリック リソースの山 (配列) を指すことができるため、このパブリック リソースの山の中で必要なリソースを見つけたい場合は、対応するリソースのみを使用する必要があります。リソース ID が必要です。このスレッドセーフ バージョンではない場合、これらのパブリック リソースはパイルに集約されず、対応する名前を通じて直接取得できます。
実行処理について
カーネルの初期化では、TSRMの初期化、カーネルに関わるパブリックリソースの登録、外部拡張に関わるパブリックリソースの登録を行います。
対応するスレッドは、PHPインタープリタ関数のエントリ位置を呼び出して、現在のスレッドのパブリックリソースデータを初期化します。
パブリック リソースが必要な場合は、対応するリソース ID を通じて取得してください。
TSRM初期化構造図
TSRMソースファイルパス
size_t size : 资源大小 ts_allocate_ctor ctor: 构造函数指针、在给每一个线程创建该资源的时候会调用一下当前ctor指针 ts_allocate_dtor dtor : 析构函数指针、释放该资源的时候会调用一下当前dtor指针 int done : 资源是否已经销毁 0:正常 1:已销毁
TSRMには主な機能が含まれます
tsrmを初期化します
typedef int ts_rsrc_id; static ts_rsrc_id id_count;
パブリックリソースを登録します
/php-5.3.27/TSRM/TSRM.c /php-5.3.27/TSRM/TSRM.h
すべてのパブリックリソースを取得して登録します、いいえ、場合存在する場合は初期化し、ストレージ ポインタを返します
tsrm_startup()
リソース ID を指定して対応するリソースを取得します
ts_allocate_id()
現在のスレッドを初期化し、既存のパブリック リソース データをストレージ ポインタにコピーします
#define TSRMLS_FETCH() void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)
TSRM いくつかの一般的なマクロ定義
#define ts_resource(id) ts_resource_ex(id, NULL)
TSRM がオンで ZTS が true の場合、拡張機能でよく見られる関数パラメーター リスト内のこれらのマクロ セットが void ***tsrm_ls に置き換えられることがわかります。実際、上記は、現在のスレッドがこの関数を呼び出し、スレッドのパブリック リソース領域アドレス &storage** を渡して、関数の内部実行プロセスが対応するスレッドのパブリック リソースを正確に取得することを保証するものです
TSRM関数呼び出しメソッド
calls
TSRMLS_FETCH() Replace void ***tsrm_ls
Execute
allocate_new_resource()
Replace
#ifdef ZTS #define TSRMLS_D void ***tsrm_ls #define TSRMLS_DC , TSRMLS_D #define TSRMLS_C tsrm_ls #define TSRMLS_CC , TSRMLS_C #else #define TSRMLS_D void #define TSRMLS_DC #define TSRMLS_C #define TSRMLS_CC #endif
TSRM 解放方法
上記のApacheのワーカーモードマルチプロセスマルチスレッドは、 1 つのプロセスが複数のスレッドを開いて PHP インタープリターを呼び出すことを意味します。スレッドが終了するたびに、現在のスレッドによって作成されたリソース データはすぐには破棄されません (スレッドはすぐに再度使用される可能性があるため、再使用する必要はありません)。 -スレッドに対応するすべてのパブリック リソース データを初期化し、直接使用できます) が、プロセスが終了しようとすると、すべてのスレッドを走査し、すべてのスレッドと対応するリソース データを解放します。
ソースコードのコメント
tsrm_startup関数の説明
-> test(int a TSRMLS_CC) -> test_1(int b TSRMLS_CC)
通常、この関数は、メモリを節約するために、デフォルトのスレッド数とリソースタイプの数が呼び出されます。後で拡張します
ts_allocate_id 関数の説明
-> test(int a ,tsrm_ls) -> test_1(int b ,tsrm_ls)
この関数は、パブリックリソースデータを登録および作成するときに呼び出す必要があります。通常、この関数はマルチスレッド環境で呼び出されることもわかります。すべてのスレッド構造ポインターと定数 ralloc および malloc を使用するため、この関数を繰り返し呼び出すとパフォーマンスが低下します。
TSRMLS_FETCH() -> ts_resource_ex 関数の説明
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename) { //省略... //默认线程数 tsrm_tls_table_size = expected_threads; //创建tsrm_tls_entry指针数组 tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *)); //省略... //全局资源唯一ID初始化 id_count=0; //默认资源类型数 resource_types_table_size = expected_resources; //省略... //创建tsrm_resource_type结构体数组 resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type)); //省略... return 1; }
allocate_new_resource 関数の説明
TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) { int i; //省略... //生成当前资源的唯一id *rsrc_id = TSRM_SHUFFLE_RSRCidD(id_count++); TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id)); //判断当前资源类型表是否小于当前资源数 //如果小于则对资源类型表进行扩容 if (resource_types_table_size < id_count) { resource_types_table = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count); //省略... resource_types_table_size = id_count; } //赋值公共资源的大小,构造函数和析构函数指针 resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].size = size; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor; resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0; //遍历说有的线程结构体,把当前创建的资源数据赋给storage指向的内存空间 for (i=0; i<tsrm_tls_table_size; i++) { tsrm_tls_entry *p = tsrm_tls_table[i]; //第一种情况 //p有可能是null,因为还没有调用 TSRMLS_FETCH() 初始化线程结构体指针 //所以 resource_types_table 就先暂时保存该资源的 size,之后等初始化 //线程结构体指针的时候,会自动在创建该公共资源的内存空间,并赋值storage //第二种情况 //已初始化对应的线程结构体指针,那么就直接根据当前新创建的资源id号对 //p->storage进行扩容,因为资源id都是递增增加的,并根据当前资源的size //malloc创建具体的资源内存空间,创建完成之后回调一下ctor while (p) { if (p->count < id_count) { int j; p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count); for (j=p->count; j<id_count; j++) { p->storage[j] = (void *) malloc(resource_types_table[j].size); if (resource_types_table[j].ctor) { resource_types_table[j].ctor(p->storage[j], &p->storage); } } //id_count每次+1 , 实际上就是我们公共资源的总数量 p->count = id_count; } //指向下一个线程结构体指针 p = p->next; } } //省略... //返回刚才id_count++ return *rsrc_id; }
拡張 TSRM の使用法
我们在开发扩展的时候也要按照线程安全版本去开发,通过 ZTS 宏判断当前 PHP 是否线程安全版本.
扩展里公共资源定义:
//定义公共资源数据,替换之后就是一个zend_模块名字的结构体 ZEND_BEGIN_MODULE_GLOBALS(module_name) int id; char name; ZEND_END_MODULE_GLOBALS(module_name) //对应的宏定义 #define ZEND_BEGIN_MODULE_GLOBALS(module_name) typedef struct _zend_##module_name##_globals { #define ZEND_END_MODULE_GLOBALS(module_name) } zend_##module_name##_globals; //替换后 typedef struct _zend_module_name_globals { int id; char name; } zend_module_name_globals;
扩展里的资源id定义
#ifdef ZTS #define ZEND_DECLARE_MODULE_GLOBALS(module_name) ts_rsrc_id module_name##_globals_id; #else #define ZEND_DECLARE_MODULE_GLOBALS(module_name) zend_##module_name##_globals module_name##_globals; #endif
(1) 线程安全版本:则自动声明全局资源唯一id,因为每个线程都会通过当前的id去storage指向内存区获取资源数据
(2)非线程安全版本:则自动声明当前结构体变量,每次通过变量名获取资源就好了,因为不存在其他线程争抢的情况
扩展里获取公共资源数据
#ifdef ZTS #define MODULE_G(v) TSRMG(xx_globals_id, zend_xx_globals *, v) #else #define MODULE_G(v) (xx_globals.v) #endif
如上每次获取资源全部通过自己定义的MODULE_G()宏获取,如果是线程安全则通过对应的TSRM管理器获取当前线程指定的资源id数据,如果不是则直接通过资源变量名字获取即可
扩展里初始化公共资源
//一般初始化公共资源数据,都会在扩展的MINIT函数执行 //如果是ZTS则ts_allocate_id调用之. PHP_MINIT_FUNCTION(myextension){ #ifdef ZTS ts_allocate_id(&xx_globals_id,sizeof(zend_module_name_globals),ctor,dtor) #endif }
结束
上面介绍的就是PHP-TSRM线程安全管理器的实现,了解TSRM之后,无论是看内核源码还是开发PHP扩展都有很大的好处,因为内核和扩展里面充斥着大量的TSRM_宏定义.
相关阅读:
以上がPHP-TSRM スレッド セーフティ マネージャー - ソース コード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。