Learn And Life.

PHP之线程资源

扩展全局资源和内核全局资源

在写php扩展的时候,我们需要使用一些线程全局的资源该如何操作呢?拿yaf来看看

1
2
3
4
5
6
7
8
9
10
11
#ifdef ZTS
#include "TSRM.h"
#endif
#ifdef ZTS
#define YAF_G(v) TSRMG(yaf_globals_id, zend_yaf_globals *, v)
#else
#define YAF_G(v) (yaf_globals.v)
#endif
extern ZEND_DECLARE_MODULE_GLOBALS(yaf);

这里定义了宏YAF_G和ZEND_DECLARE_MODULE_GLOBALS,我们先看看ZEND_DECLARE_MODULE_GLOBALS

1
2
3
4
5
6
7
8
9
#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

可以看到在线程安全的环境下,ZEND_DECLARE_MODULE_GLOBALS(module_name)被定义成为ts_rsrc_id类型的,再来看看TSRMG

1
#define TSRMG(id, type, element) (TSRMG_BULK(id, type)->element)

其中TSRMG_BULK

1
#define TSRMG_BULK(id, type) ((type) (*((void ***) tsrm_get_ls_cache()))[TSRM_UNSHUFFLE_RSRC_ID(id)])

TSRM_UNSHUFFLE_RSRC_ID是对id进行运算,暂且不管,可以看到最终的调用者tsrm_get_ls_cache(),同样还是来看看这个函数和其内部调用的tsrm_tls_get()函数

1
2
3
4
5
6
TSRM_API void *tsrm_get_ls_cache(void)
{/*{{{*/
return tsrm_tls_get();
}/*}}}*
# define tsrm_tls_get() pthread_getspecific(tls_key)

哦, 看到这里,你会发现pthread_getspecific(),一定存在pthread_setspecific(),我们再来看看pthread_setspecific()

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# define tsrm_tls_set(what) pthread_setspecific(tls_key, (void*)(what))
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
{
.....
(*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry));
(*thread_resources_ptr)->storage = NULL;
if (id_count > 0) {
(*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count);
}
(*thread_resources_ptr)->count = id_count;
(*thread_resources_ptr)->thread_id = thread_id;
(*thread_resources_ptr)->next = NULL;
/* Set thread local storage to this new thread resources structure */
tsrm_tls_set(*thread_resources_ptr);
if (tsrm_new_thread_begin_handler) {
tsrm_new_thread_begin_handler(thread_id);
}
for (i=0; i<id_count; i++) {
if (resource_types_table[i].done) {
(*thread_resources_ptr)->storage[i] = NULL;
} else
{
(*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size);
if (resource_types_table[i].ctor) {
resource_types_table[i].ctor((*thread_resources_ptr)->storage[i]);
}
}
}
if (tsrm_new_thread_end_handler) {
tsrm_new_thread_end_handler(thread_id);
}
tsrm_mutex_unlock(tsmm_mutex);
......
}

可以看到在分配一个新的资源的时候,会调用tsrm_tls_set(*thread_resources_ptr),而thread_resources_ptr是一个指向线程资源对象tsrm_tls_entry的数组指针,tsrm_tls_entry对象的结构体如下

1
2
3
4
5
6
struct _tsrm_tls_entry {
void **storage;
int count;
THREAD_T thread_id;
tsrm_tls_entry *next;
};

可以看到真正资源数据存储在storage指针数组里,那么storage存储的是什么数据呢?

1
resource_types_table[i].ctor((\*thread_resources_ptr)->storage[i]);

这里调用了每种资源的构造方法,看看yaf是如何来注册其全局资源对象的

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ZEND_BEGIN_MODULE_GLOBALS(yaf)
zend_string *ext;
zend_string *base_uri;
zend_string *directory;
zend_string *local_library;
zend_string *local_namespaces;
zend_string *view_directory;
zend_string *view_ext;
zend_string *default_module;
zend_string *default_controller;
zend_string *default_action;
zend_string *bootstrap;
char *global_library;
char *environ_name;
char *name_separator;
size_t name_separator_len;
zend_bool lowcase_path;
zend_bool use_spl_autoload;
zend_bool throw_exception;
zend_bool action_prefer;
zend_bool name_suffix;
zend_bool autoload_started;
zend_bool running;
zend_bool in_exception;
zend_bool catch_exception;
zend_bool suppressing_warning;
/\* {{{ This only effects internally \*/
zend_bool st_compatible;
/\* }}} \*/
long forward_limit;
HashTable *configs;
zval modules;
zval *default_route;
zval active_ini_file_section;
zval *ini_wanted_section;
uint parsing_flag;
zend_bool use_namespace;
ZEND_END_MODULE_GLOBALS(yaf)
#define ZEND_BEGIN_MODULE_GLOBALS(module_name) \
typedef struct _zend_##module_name##_globals {
#define ZEND_END_MODULE_GLOBALS(module_name) \
} zend_##module_name##_globals;

可以看到它申明了zen_yaf_globals结构体对象,而其实yaf是不支持多线程环境下运行的,zen_yaf_globals只是在扩展内部使用

1
ZEND_DECLARE_MODULE_GLOBALS(yaf);

但是cgi, php-fpm, apache2handler是支持多线程的

1
2
3
4
5
root@chenjingxiu:~/php-src# egrep -Ern "ts_allocate_id"|grep sapi
main/SAPI.c:84: ts_allocate_id(&sapi_globals_id, sizeof(sapi_globals_struct), (ts_allocate_ctor) sapi_globals_ctor, (ts_allocate_dtor) sapi_globals_dtor);
sapi/cgi/cgi_main.c:1801: ts_allocate_id(&php_cgi_globals_id, sizeof(php_cgi_globals_struct), (ts_allocate_ctor) php_cgi_globals_ctor, NULL);
sapi/fpm/fpm/fpm_main.c:1500: ts_allocate_id(&php_cgi_globals_id, sizeof(php_cgi_globals_struct), (ts_allocate_ctor) php_cgi_globals_ctor, NULL);
sapi/apache2handler/php_functions.c:543: ts_allocate_id(&php_apache2_info_id, sizeof(php_apache2_info_struct), (ts_allocate_ctor) NULL, NULL);

这些模块中都会定义自己的析构方法,比如cgi和fpm都是php_cgi_globals_ctor,全局资源类型都是php_cgi_globals_struct

线程全局资源原理

先看下面的代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<stdio.h>
#include<pthread.h>
#include<string.h>
/**
* 线程内模块数据共享
* 功能: 共享全局唯一标识
*/
pthread_key_t global_id_key;
typedef struct global_id
{
char *name;
int len;
} global_id;
/* 读共享数据 */
void *get_cache_id()
{
return pthread_getspecific(global_id_key);
}
/* 写共享数据 */
void *thread_global_run(void *args)
{
pthread_setspecific(global_id_key,args);
#ifdef DEBUG
global_id *id = (global_id*)pthread_getspecific(global_id_key);
printf("params | name:%s len:%d\n", id->name, id->len);
global_id *cache_id = (global_id *)get_cache_id();
printf("the result | name:%s len:%d\n", cache_id->name, cache_id->len);
#endif
return pthread_getspecific(global_id_key);
}
int main(int argv, char **argc)
{
pthread_t p;
global_id id = {"kivmi", 5};
pthread_key_create(&global_id_key, NULL);
pthread_create(&p, NULL, thread_global_run, &id);
pthread_join(p, NULL);
return 0;
}

从中可以看到pthread_setspecific会将引用类型的参数临时存起来,在thread_global_run函数里可以做一些处理,当然DEBUG模块可以处理相当复杂的业务,这里只做了简单的处理,比如其它模块间资源的共享处理,看下运行结果

1
2
3
4
root@chenjingxiu:~/project# gcc specific.c -o specific -lpthread -DDEBUG
root@chenjingxiu:~/project# ./specific
params | name:kivmi len:5
the result | name:kivmi len:5

当然,这里只是关于线程资源的最简单的方式,php中线程全局资源的管理由于涉及到各种资源类型的管理,比这复杂的多!