ホームページ  >  記事  >  バックエンド開発  >  PHP-TSRM スレッド セーフティ マネージャー - ソース コード分析

PHP-TSRM スレッド セーフティ マネージャー - ソース コード分析

*文
*文オリジナル
2017-12-21 17:08:531538ブラウズ

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 公共资源类型结构体、注册了多少公共资源就有多少个该结构体
rreee

フィールドの説明

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初期化構造図


PHP-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 cgi与fpm关系

PHP CGI FastCGI php-fpm 解惑

以上がPHP-TSRM スレッド セーフティ マネージャー - ソース コード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。