欢迎转载,转载请注明出处http://blog.csdn.net/hackooo/article/details/8702156 谢谢!新浪微博:小灰马

0.前言

相信很多人跟我一样,一开始看PHP源码的时候看到一堆的TSRM_CC,TSRM_DC特别蛋疼,大多数函数的声明都会在参数末尾加个TSRM_DC,着实让像我这样以前没搞过多线程编程的很不理解。网上找了找,介绍这方面的材料非常少,只找到@张洋的一篇文章,《PHP及Zend Engine的线程安全模型》 。不过看了他的文章,看完还是有很多疑问,他的文章也没仔细说清楚,那就只能自己看源码啦!先是去看了《多线程程序设计》这本书,然后再扫一扫PHP的线程安全相关的源码,算是有点眉目,对@张洋的文章里面提的一些问题也有了自己一些见解。以下是个人一些拙见,若有不妥的地方,欢迎交流讨论,谢谢~

1.线程私有数据

我们知道,在多线程环境中,线程之间是共享内存的地址空间的,那么保证线程之间的各自的私有数据读写不相互干扰就显得非常重要。那么这个需求如何实现呢?有个非常简单的办法(当然还有更好的实现),举个例子,

假如现在有三个线程 thread1,thread2,thread3,

我们声明一个简单的全局的数组变量,不妨叫 global_arr,

那么各个线程每次读写自己的数据的时候提供一个自己的线程id,到global_arr里面取不就得了,global_arr[0],global_arr[1] ... ,非常简单吧...

不过显然这样做是不太完善的,global_arr是个全局变量,暴露给了所有的线程,要是某个thread由于种种原因,提供一个错误的id去global_arr里面取数据,那结果肯定是很严重的。

不同的系统环境有多种不同的多线程的实现,以pthread为例,它提供了以下几个相关的结构和api:

pthread_key_t key;                                                        //线程的一个key
int pthread_key_create(pthread_key *key,void(*destructor)(void *));       //初始化一个key
int pthread_key_delete(pthread_key_t key);                                //删除一个key
int pthread_setspecific(pthread_key_t key,const void *value);             //为一个key指定value
void *pthread_getspecific(pthread_key_t key);                             //获取一个key指向的value

这样,可以把pthead对线程私有数据实现理解为:提供一个简单的key-value实现,而且各个线程不相互干扰。可以在线程初始化的时候声明一个key,这里的key指向一个大一点的数据结构,线程把要保存的一堆私有数据全丢里面去,以后线程就用一个key把这个结构找到,然后再在里面找具体的item,这样就不用说每次要保存一个小的私有数据就声明一个key,因为这样太浪费资源了。

2.PHP中的线程安全实现

      2.1总体结构:

tsrm_tls_table (意思应该是tsrm thread local storage table)是个哈希表,我们姑且称之为“全局线程私有数据表”,存放的是每个线程私有数据的指针。

这个哈希表的算法非常简单,就是最常用的取余数法,解决哈希冲突的方法采用了拉链法。

# /PHP_5_4/TSRM/TSRM.h  //thr是线程的thread_id,ts是tsrm_tls_table_size,即全局线程私有数据表的大小。
100#define THREAD_HASH_OF(thr,ts)  (unsigned long)thr%(unsigned long)ts

tsrm_tls_entry就是每个线程私有数据的入口啦,从名字就能看出来:tsrm thread local storage entry。

# /PHP_5_4/TSRM/TSRM.c
typedef struct _tsrm_tls_entry tsrm_tls_entry;struct _tsrm_tls_entry {void **storage;        //存放一个一个resource的指针int count;             //多少个resouceTHREAD_T thread_id;    //线程的idtsrm_tls_entry *next;  //下个entry的指针
};

第一节提到的线程去取自己的私有数据是使用一个key去取的,这里key指向的value就是一个tsrm_tls_entry的指针,也就是说每个线程每次取自己的私有数据的时候,是通过一个key,从tsrm_tls_table取到属于自己的tsrm_tls_entry的结构。

PHP中的tsrm_tls_entry存放的是一些resource,每个线程都有一些功能一样的resource,例如,都需要个zend_executor_globals结构,用来存放一些运行时的“全局变量”,但是每个线程的运行环境不一样,使用的zend_executor_globals肯定不能是同一个吧,因此实际上,每个线程都有这些resource的一份拷贝。

我们看下tsrm_resource_type的结构:

typedef struct {size_t size;            //所占内存大小ts_allocate_ctor ctor;  //constructorts_allocate_dtor dtor;  //destructorint done;               //是否已经结束利用,释放掉了
} tsrm_resource_type;

可以看到,TSRM中对资源的描述非常简单,一个描述这块内存大小,一个构造函数,一个析构函数,还有一个标志位标识使用情况。

上面的tsrm_tls_entry通过storage成员指向一个一个实际的resource:

static tsrm_resource_type   *resource_types_table=NULL;
static int                  resource_types_table_size;

按照上图那样,tsrm_tls_entry的storage存放的是一些资源的指针,resource_pointer指向的实际的内存块是依据resource_type_table一个个资源描述结构 构造出来的。这样看,resource_type_table是不是像一个模具,然后线程就依据这个模具造出一个个私有数据出来呢?举个形象的例子,这个resource_type_table就像一个存放房屋工程设计图的本本,里面存放一张张工程设计的图纸,图纸内容分为四类:1.要建造的房子的大小;2.房子怎么建造;3.房子旧了怎么拆掉;4.这张图纸是否过时了。而每一个线程像一个个房屋建造的团队,它们在全国各地根据一张张的图纸,把一个个房子给建起来。以后就把房子卖给不同的人群做不同的事(租地下室给码农啦,投资增值啦...尼玛不说了)。

2.2相关的API及算法:

首先把大体的api列一列:

/* Startup TSRM (call once for the entire process) TSRM启动函数,整个生命周期只执行一遍,前两个参数:1.要预启动多少个线程2.要预分配多少个资源*/
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename)/* Shutdown TSRM (call once for the entire process) TSRM关闭函数,整个生命周期只执行一遍*/
TSRM_API void tsrm_shutdown(void)/* allocates a new thread-safe-resource id ,往resource_type_table里添加一种资源类型,各个线程按这个类型分配一块内存块,返回这个资源类型的id*/
TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)/* 生成一个新的线程私有数据结构,并按照resource_type_table把资源分配给它 */
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)/* fetches the requested resource for the current thread 根据资源的id返回指定线程拥有的的资源*/
TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)/* 下面这几个跟上下文相关的api是给那些对整个线程安全模型了如指掌的开发人员用的。* frees an interpreter context.  You are responsible for making sure that* it is not linked into the TSRM hash, and not marked as the current interpreter * 释放一个上下文,你必须确保它没链到全局的tsrm_tls_table以及没有把它当做当前线程的上下文。*/
void tsrm_free_interpreter_context(void *context)
void *tsrm_set_interpreter_context(void *new_ctx) /* 设置当前线程的上下文,并把老的上下文返回*/
void *tsrm_new_interpreter_context(void)          /* 设置一个新的上下文,用到上面的tsrm_set_interpreter_context */
void ts_free_thread(void)                         /* 把当前的线程的所占的resource都释放掉。*/
void ts_free_worker_threads(void)                 /* 把除了当前线程的其它线程的resource都释放掉*/
void ts_free_id(ts_rsrc_id id)                    /* 把resource_type_table里面指定资源id的资源(包括所有线程中,资源id为此id的资源)全释放掉,并标记done *//*下面是一些简单的工具*/
TSRM_API THREAD_T tsrm_thread_id(void)          /* 获得当前线程的id */
TSRM_API MUTEX_T tsrm_mutex_alloc(void)         /* 分配一个锁*/
TSRM_API void tsrm_mutex_free(MUTEX_T mutexp)   /* 删除锁*/
TSRM_API int tsrm_mutex_lock(MUTEX_T mutexp)    /* 加锁 */
TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp)  /* 解锁*/
TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset)  /* 信号相关 */
/*设置线程初始句柄*/
TSRM_API void *tsrm_set_new_thread_begin_handler(tsrm_thread_begin_func_t new_thread_begin_handler)
/*设置线程结束句柄*/
TSRM_API void *tsrm_set_new_thread_end_handler(tsrm_thread_end_func_t new_thread_end_handler)
/*DEBUG支持*/
int tsrm_error(int level, const char *format, ...)
void tsrm_error_set(int level, char *debug_filename)

下面结合源码一个一个看TSRM怎么实现的:

/* Startup TSRM (call once for the entire process) */
/* tsrm启动函数 */
TSRM_API int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename)
{
#if defined(GNUPTH)pth_init();
#elif defined(PTHREADS)pthread_key_create( &tls_key, 0 );       //初始化线程的tls_key
#elif defined(TSRM_ST)st_init();st_key_create(&tls_key, 0);
#elif defined(TSRM_WIN32)tls_key = TlsAlloc();
#elif defined(BETHREADS)tls_key = tls_allocate();
#endiftsrm_error_file = stderr;tsrm_error_set(debug_level, debug_filename);//初始化全局线程私有数据表tsrm_tls_table_size = expected_threads;tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *));if (!tsrm_tls_table) {TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate TLS table"));return 0;}//初始化全局资源表,id_count是一个计数器的全局变量id_count=0;resource_types_table_size = expected_resources;resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type));if (!resource_types_table) {TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate resource types table"));free(tsrm_tls_table);tsrm_tls_table = NULL;return 0;}//初始化锁tsmm_mutex = tsrm_mutex_alloc();//初始化线程的开始和结束句柄tsrm_new_thread_begin_handler = tsrm_new_thread_end_handler = NULL;TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Started up TSRM, %d expected \threads, %d expected resources", expected_threads, expected_resources));return 1;
}/* Shutdown TSRM (call once for the entire process) TSRM结束函数,做一些内存清理工作*/
TSRM_API void tsrm_shutdown(void)
{int i;//把tsrm_tls_table所有线程的私有数据全部干掉if (tsrm_tls_table) {for (i=0; i<tsrm_tls_table_size; i++) {tsrm_tls_entry *p = tsrm_tls_table[i], *next_p;while (p) {int j;next_p = p->next;for (j=0; j<p->count; j++) {if (p->storage[j]) {if (resource_types_table && !resource_types_table[j].done && resource_types_table[j].dtor) {resource_types_table[j].dtor(p->storage[j], &p->storage);}free(p->storage[j]);}}free(p->storage);free(p);p = next_p;}}free(tsrm_tls_table);tsrm_tls_table = NULL;}//释放资源表if (resource_types_table) {free(resource_types_table);resource_types_table=NULL;}//释放锁tsrm_mutex_free(tsmm_mutex);tsmm_mutex = NULL;TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Shutdown TSRM"));if (tsrm_error_file!=stderr) {fclose(tsrm_error_file);}//删除线程的key
#if defined(GNUPTH)pth_kill();
#elif defined(PTHREADS)pthread_setspecific(tls_key, 0);pthread_key_delete(tls_key);
#elif defined(TSRM_WIN32)TlsFree(tls_key);
#endif
}/* 往resource_type_table里添加一种资源类型,各个线程按这个类型分配一块内存块,返回这个资源类型的id*/
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;TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new resource id, %d bytes", size));//加锁tsrm_mutex_lock(tsmm_mutex);/* 获得一个资源的id ,这里为啥要用到TSRM_SHUFFLE_RSRC_ID呢,目的是为了与resource_types_table_size对应,*  resource_types_table[0] 存放第一个资源,rsrc_id = id_count = 1 ,resource_types_table_size = 1;*  resource_types_table[1] 存放第二个资源,rsrc_id = id_count = 2 ,resource_types_table_size = 2;*  resource_types_table[2] 存放第三个资源,rsrc_id = id_count = 3 ,resource_types_table_size = 3;*  实际上,根据id_count的名称,作者的本意应该是用这个变量来计数有多少个资源的id的,而每个线程的tsrm_tls_entry也有个count成员,与这个是对应的。*  这样 id_count 与 resource_types_table_size就能进行直接比较,而且保证返回的rsrc_id大于0,*  只不过在实际保存到resource_types_table里的时候,要把id unshuffle一下,也就是减一操作,因为索引是从0开始的。*/*rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id));/* 全局资源表扩容,添加一个新的资源类型*//* store the new resource type in the resource sizes table */if (resource_types_table_size < id_count) {resource_types_table = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count);if (!resource_types_table) {tsrm_mutex_unlock(tsmm_mutex);TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource"));*rsrc_id = 0;return 0;}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;/* 把所有已经active的线程的资源根据id_count扩容,* 也就是说,假如入原来线程只保存了1~3三个资源,现在id_count如果经过一些操作,增加到5个了,那么线程必须把4和5两个资源给补齐。*//* enlarge the arrays for the already active threads */for (i=0; i<tsrm_tls_table_size; i++) {tsrm_tls_entry *p = tsrm_tls_table[i];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);}}p->count = id_count;}p = p->next;}}tsrm_mutex_unlock(tsmm_mutex);TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new resource id %d", *rsrc_id));return *rsrc_id;
}/* 生成一个新的线程私有数据结构,并按照resource_type_table把资源分配给它
*  注意,首先扫下这个函数,并没有加锁操作,只有解锁操作,所以,使用这个函数前应首先加锁 tsmm_mutex
*  后面的ts_resource_ex确实就是这么干的。
*/
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
{int i;TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Creating data structures for thread %x", thread_id));/*生成一个线程的私有数据结构,叫资源入口吧*/(*thread_resources_ptr) = (tsrm_tls_entry *) malloc(sizeof(tsrm_tls_entry));(*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 *//把当前线程的key指向这个资源入口/tsrm_tls_set(*thread_resources_ptr);if (tsrm_new_thread_begin_handler) {tsrm_new_thread_begin_handler(thread_id, &((*thread_resources_ptr)->storage));}/* 按照resrouce_types_table 把所有资源给拷贝一份,那些done的不拷。*/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], &(*thread_resources_ptr)->storage);}}}if (tsrm_new_thread_end_handler) {tsrm_new_thread_end_handler(thread_id, &((*thread_resources_ptr)->storage));}tsrm_mutex_unlock(tsmm_mutex);
}/* 根据资源的id返回指定线程拥有的的资源 */
TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
{THREAD_T thread_id;int hash_value;tsrm_tls_entry *thread_resources;/* 这个 NETWART 表示还没搞清楚什么情况下回触发,待研究,mark一下========================================*/
#ifdef NETWARE/* The below if loop is added for NetWare to fix an abend while unloading PHP* when an Apache unload command is issued on the system console.* While exiting from PHP, at the end for some reason, this function is called* with tsrm_tls_table = NULL. When this happened, the server abends when* tsrm_tls_table is accessed since it is NULL.*/if(tsrm_tls_table) {
#endif/* 如果没传线程的id进来,就默认是访问当前线程 */if (!th_id) {/* Fast path for looking up the resources for the current* thread. Its used by just about every call to* ts_resource_ex(). This avoids the need for a mutex lock* and our hashtable lookup.*//* 快速获取线程的私有数据的指针,放在key里面 ,防止每次都去tsrm_tls_table里面找一遍,因为这个table是个哈希表,找起来还是蛮消耗资源的*/thread_resources = tsrm_tls_get();if (thread_resources) {TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Fetching resource id %d \for current thread %d", id, (long) thread_resources->thread_id));/* Read a specific resource from the thread's resources.* This is called outside of a mutex, so have to be aware about external* changes to the structure as we read it.*/TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);}thread_id = tsrm_thread_id();} else {thread_id = *th_id;}/* 在线程的key里面没找着,原因可能是还没为这个线程分配资源呢!*/TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Fetching resource id %d for thread %ld", id, (long) thread_id));/* 加锁 */tsrm_mutex_lock(tsmm_mutex);hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);thread_resources = tsrm_tls_table[hash_value];if (!thread_resources) {/* 还没为这个线程分配资源,分配之~*/allocate_new_resource(&tsrm_tls_table[hash_value], thread_id);return ts_resource_ex(id, &thread_id);} else {/* 在同一个hash值的单链表里,找之~ 如果这个链表里也没有,分配之~*/do {if (thread_resources->thread_id == thread_id) {break;}if (thread_resources->next) {thread_resources = thread_resources->next;} else {allocate_new_resource(&thread_resources->next, thread_id);return ts_resource_ex(id, &thread_id);/** thread_resources = thread_resources->next;* break;*/}} while (thread_resources);}tsrm_mutex_unlock(tsmm_mutex);/* Read a specific resource from the thread's resources.* This is called outside of a mutex, so have to be aware about external* changes to the structure as we read it.*/TSRM_SAFE_RETURN_RSRC(thread_resources->storage, id, thread_resources->count);
#ifdef NETWARE}   /* if(tsrm_tls_table) */
#endif
}/* frees an interpreter context.  You are responsible for making sure that* it is not linked into the TSRM hash, and not marked as the current interpreter */
/* 把一个线程的资源上下文释放掉,当然,必须保证它没用了,也就是即不是当前线程的上下文,也不是别的线程的上下文。 */
void tsrm_free_interpreter_context(void *context)
{tsrm_tls_entry *next, *thread_resources = (tsrm_tls_entry*)context;int i;while (thread_resources) {next = thread_resources->next;for (i=0; i<thread_resources->count; i++) {if (resource_types_table[i].dtor) {resource_types_table[i].dtor(thread_resources->storage[i], &thread_resources->storage);}}for (i=0; i<thread_resources->count; i++) {free(thread_resources->storage[i]);}free(thread_resources->storage);free(thread_resources);thread_resources = next;}
}
/* 为当前线程设置一个新的上下文,并把老的上下文的指针返回 */
void *tsrm_set_interpreter_context(void *new_ctx)
{tsrm_tls_entry *current;current = tsrm_tls_get();/* TODO: unlink current from the global linked list, and replace it* it with the new context, protected by mutex where/if appropriate *//* Set thread local storage to this new thread resources structure */tsrm_tls_set(new_ctx);/* return old context, so caller can restore it when they're done */return current;
}/* 这个函数相当于用上面的函数生成一个新的上下文,
*  然后当前线程切换回原来的上下文,返回这个新的上下文的指针,相当于拷贝了一份自己的孪生兄弟出来。 */
void *tsrm_new_interpreter_context(void)
{tsrm_tls_entry *new_ctx, *current;THREAD_T thread_id;thread_id = tsrm_thread_id();tsrm_mutex_lock(tsmm_mutex);current = tsrm_tls_get();allocate_new_resource(&new_ctx, thread_id);/* switch back to the context that was in use prior to our creation* of the new one */return tsrm_set_interpreter_context(current);
}/* 把当前线程的资源都释放掉 */
void ts_free_thread(void)
{tsrm_tls_entry *thread_resources;int i;THREAD_T thread_id = tsrm_thread_id();int hash_value;tsrm_tls_entry *last=NULL;tsrm_mutex_lock(tsmm_mutex);hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);thread_resources = tsrm_tls_table[hash_value];while (thread_resources) {if (thread_resources->thread_id == thread_id) {for (i=0; i<thread_resources->count; i++) {if (resource_types_table[i].dtor) {resource_types_table[i].dtor(thread_resources->storage[i], &thread_resources->storage);}}for (i=0; i<thread_resources->count; i++) {free(thread_resources->storage[i]);}free(thread_resources->storage);if (last) {last->next = thread_resources->next;} else {tsrm_tls_table[hash_value] = thread_resources->next;}tsrm_tls_set(0);free(thread_resources);break;}if (thread_resources->next) {last = thread_resources;}thread_resources = thread_resources->next;}tsrm_mutex_unlock(tsmm_mutex);
}/* 把除当前线程外的其它线程的资源都释放掉 */
void ts_free_worker_threads(void)
{tsrm_tls_entry *thread_resources;int i;THREAD_T thread_id = tsrm_thread_id();int hash_value;tsrm_tls_entry *last=NULL;tsrm_mutex_lock(tsmm_mutex);hash_value = THREAD_HASH_OF(thread_id, tsrm_tls_table_size);thread_resources = tsrm_tls_table[hash_value];while (thread_resources) {if (thread_resources->thread_id != thread_id) {for (i=0; i<thread_resources->count; i++) {if (resource_types_table[i].dtor) {resource_types_table[i].dtor(thread_resources->storage[i], &thread_resources->storage);}}for (i=0; i<thread_resources->count; i++) {free(thread_resources->storage[i]);}free(thread_resources->storage);if (last) {last->next = thread_resources->next;} else {tsrm_tls_table[hash_value] = thread_resources->next;}free(thread_resources);if (last) {thread_resources = last->next;} else {thread_resources = tsrm_tls_table[hash_value];}} else {if (thread_resources->next) {last = thread_resources;}thread_resources = thread_resources->next;}}tsrm_mutex_unlock(tsmm_mutex);
}/* 干掉一个资源,并在全局资源表中标记为done */
void ts_free_id(ts_rsrc_id id)
{int i;int j = TSRM_UNSHUFFLE_RSRC_ID(id);tsrm_mutex_lock(tsmm_mutex);TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Freeing resource id %d", id));if (tsrm_tls_table) {for (i=0; i<tsrm_tls_table_size; i++) {tsrm_tls_entry *p = tsrm_tls_table[i];while (p) {if (p->count > j && p->storage[j]) {if (resource_types_table && resource_types_table[j].dtor) {resource_types_table[j].dtor(p->storage[j], &p->storage);}free(p->storage[j]);p->storage[j] = NULL;}p = p->next;}}}resource_types_table[j].done = 1;tsrm_mutex_unlock(tsmm_mutex);TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully freed resource id %d", id));
}

欢迎转载,转载请注明出处http://blog.csdn.net/hackooo/article/details/8702156 谢谢!新浪微博:小灰马

PHP源码分析之线程安全模型相关推荐

  1. 线程资源PHP源码分析之线程安全模型

    文章结束给大家来个程序员笑话:[M] 迎欢载转,载转请注明出处http://blog.csdn.net/hackooo/article/details/8702156 感谢!新浪微博:小灰马 0.媒介 ...

  2. 深入源码分析Java线程池的实现原理

    转载自   深入源码分析Java线程池的实现原理 程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对 ...

  3. Hbase Compaction 源码分析 - CompactSplitThread 线程池选择

    目录 CompactSplitThread requestCompactionInternal方法 selectCompaction方法 requestCompaction方法 其他相关文章 Hbas ...

  4. 从原理到实现丨手把手教你写一个线程池丨源码分析丨线程池内部组成及优化

    人人都能学会的线程池 手写完整版 1. 线程池的使用场景 2. 线程池的内部组成 3. 线程池优化 [项目实战]从原理到实现丨手把手教你写一个线程池丨源码分析丨线程池内部组成及优化 内容包括:C/C+ ...

  5. Libuv源码分析 —— 8. 线程池

    网络I/O 在 上一节 的学习中,我们已经搞明白了网络I/O的基本过程,并通过了解进程/线程间通信来熟悉这个流程.下面,让咱们学习线程池中的线程如何工作.并和主进程进行通信的吧! 线程池 Libuv ...

  6. 从源码分析创建线程池的4种方式

    摘要:从创建线程池的源码来深入分析究竟有哪些方式可以创建线程池. 本文分享自华为云社区<[高并发]从源码角度分析创建线程池究竟有哪些方式>,作者:冰 河 . 在Java的高并发领域,线程池 ...

  7. JDK1.8源码分析:线程安全的CopyOnWriteArrayList与CopyOnWriteArraySet

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:服务端开发 blog.csdn.net/u01001 ...

  8. c++ 线程池_JAVA并发编程:线程池ThreadPoolExecutor源码分析

    前面的文章已经详细分析了线程池的工作原理及其基本应用,接下来本文将从底层源码分析一下线程池的执行过程.在看源码的时候,首先带着以下两个问题去仔细阅读.一是线程池如何保证核心线程数不会被销毁,空闲线程数 ...

  9. threadpoolexecutor创建线程池_线程池ThreadPoolExecutor源码分析

    什么是线程池 创建线程要花费昂贵的资源和时间,如果任务来了才创建那么响应时间会变长,而且一个进程能创建的线程数量有限.为了避免这些问题,在程序启动的时候就创建若干线程来响应出来,它们被称为线程池,里面 ...

最新文章

  1. python 类的绑定方法和非绑定方法
  2. Unable to Connect: sPort: 0 C# ServiceStack.Redis 访问 redis
  3. OpenCV人脸检测与人脸识别
  4. spring 循环依赖注入
  5. mysql 未知列_mysql – ‘字段列表’连接中的未知列’..’
  6. 一名英格兰球迷眼里的本届英格兰队
  7. 漫画:什么是布隆算法?
  8. c语言编写计算器保存结果的程序,c语言编写计算器程序.doc
  9. python dataframe索引转成列_Pandas之DataFrame对象的列和索引之间的转化
  10. Linux内核对per-cpu变量的实现
  11. jquery dropload
  12. xshell报initialize flexnet service failed error code 50003错误
  13. Excel宏批量转置并删除空格
  14. 你可能不知道的印度手机市场
  15. 【JZOJ 省选模拟】死星(deathstar )
  16. D. Lizard Era: Beginning
  17. 会议签到web_基于Web的网络签到系统设计与实现
  18. UML图之类图,对象图和包图
  19. win2003 apache php5.4 mysql_win2003下Apache2.4+PHP5.4+mysql5.6的搭建
  20. Visio用UML2.2模板包

热门文章

  1. 钻井缸套排量_钻井队用排量计算公式
  2. 姗姗来迟的2022年终总结
  3. 泡泡龙——代码结构规范
  4. App应用接口版本兼容设计和使用原则
  5. Leetcode 621
  6. CVPR2022论文速递(2022.4.1)!共33篇,已分类!
  7. 微信小程序扫描二维码条形码
  8. python3爬取网页数据学习笔记——XPath篇
  9. 转帖一篇---Noesis:一款基于本体的大气科学语义搜索工具
  10. FOR循环及基础应用方式(输出一组字符串)