神的尾巴

全栈工程师、独立开发者

0%

聊聊PHP线程安全

记得最早接触 PHP 的时候,经常会纠结是用 TS 还是 NTS 版的

线程安全

线程安全问题在以下情况会发生:

  1. 有共享变量
  2. 多线程
  3. 有修改共享变量

解决思路如下:

  1. 操作原子化:加锁,使用原子类
  2. 每个线程 copy 一份共享变量,互不影响
  3. 使用局部变量代替

TSRM

TSRM: 线程安全资源管理器(Thread Safe Resource Manager)

主要流程如下:

通过定义宏,未开启了 TSRM 的情况下,直接定义全局变量,如果开启的 TSRM,获取资源 ID,添加到资源 table,到内存管理 table 为每个线程分配一份该资源 size 的内存。

代码分析如下

截取了部分 TSRM 的源码(5.6 版本)

用的数据结构有三个:

  1. id_count:存储目前资源 id 的数量
  2. tsrm_resource_type:存储资源 id 对应资源的信息,资源大小,构造方法指针,析构方法指针等
  3. 维护存储所有线程中的资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 资源table
typedef struct {
size_t size;//资源的大小
ts_allocate_ctor ctor;//构造方法指针
ts_allocate_dtor dtor;//析构方法指针
int done;
} tsrm_resource_type;
static int resource_types_table_size;
static tsrm_resource_type *resource_types_table=NULL;

// 内存管理table,每个entry对应一个线程安全的全局变量
struct _tsrm_tls_entry {
void **storage; // 储存所有线程的数据
int count; // 存储线程的数量
THREAD_T thread_id;
tsrm_tls_entry *next;
};
static int tsrm_tls_table_size;
static tsrm_tls_entry **tsrm_tls_table=NULL;

// 资源id,int类型
typedef int ts_rsrc_id;
static ts_rsrc_id id_count;

为了确保资源 id 不冲突,对 id 加锁

1
2
3
4
5
6
7
8
9
// 在获取资源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)
{
tsrm_mutex_lock(tsmm_mutex);
// ...
*rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
// ...
tsrm_mutex_unlock(tsmm_mutex);
}

为了优化效率,在初始化的时候,也以传入 expected_threds,expected_resources 预先分配资源。

1
2
3
4
5
// 为了优化效率,可以设置参数expected_threds,expected_resources,提前分配资源
tsrm_tls_table_size = expected_threads;
tsrm_tls_table = (tsrm_tls_entry **) calloc(tsrm_tls_table_size, sizeof(tsrm_tls_entry *));
resource_types_table_size = expected_resources;
resource_types_table = (tsrm_resource_type *) calloc(resource_types_table_size, sizeof(tsrm_resource_type));

参考文章

  1. PHP 中的线程安全
  2. 揭秘 TSRM-鸟哥
觉得对你有帮助的话,请我喝杯咖啡吧~.