关于php的共享内存的使用和研究之深入剖析swoole table

上文:
关于php的共享内存的使用和研究之由起
关于php的共享内存的使用和研究之外部存储

话说回来,究竟swoole的底层是怎么做到了使用行锁,来实现进程访问冲突解决与高性能的呢?这里确实值得研究一下。

首先来看一下swooletable中用来存储的基本数据结构swTableRow:

typedef struct _swTableRow
{
    sw_atomic_t lock;// 原子锁,所谓的效率更高的行锁,这个要等下看看了。
    /**
     * 1:used, 0:empty
     */
    uint8_t active;//是否启用状态
    /**
     * next slot
     */
    struct _swTableRow *next;//链表结构
    /**
     * Hash Key
     */
    char key[SW_TABLE_KEY_SIZE];//大小64,意味着单哈希key的长度
    char data[0];//真实数据
} swTableRow;

然后是用来遍历行的索引数据结构swTable_iterator:

typedef struct
{
    uint32_t absolute_index;
    uint32_t collision_index;
    swTableRow *row;
} swTable_iterator;

然后是包含了多行内容的swTable:

typedef struct
{
    swHashMap *columns;// 一个table,包含多列中的列信息
    uint16_t column_num;
    swLock lock;
    uint32_t size;
    uint32_t mask;
    uint32_t item_size;

    /**
     * total rows that in active state(shm)
     */
    sw_atomic_t row_num;

    swTableRow **rows;// 一列包含多行,所以是个二维的数组
    swMemoryPool *pool;

    uint32_t compress_threshold;

    swTable_iterator *iterator;

    void *memory;
} swTable;

用来存储swooletable中每一列信息的swTableColumn:

typedef struct
{
   uint8_t type; // 结构类型,可选是int、浮点、字符串
   uint32_t size; // 声明的大小,

   swString* name;
   uint16_t index;
} swTableColumn;

// 此结构体即为执行$_swooleTable->column('ip',\swoole_table::TYPE_STRING, 64)时设置结构体中内容

几个对外暴露的api如下:

swTable* swTable_new(uint32_t rows_size);
int swTable_create(swTable *table);
void swTable_free(swTable *table);
int swTableColumn_add(swTable *table, char *name, int len, int type, int size);
swTableRow* swTableRow_set(swTable *table, char *key, int keylen, sw_atomic_t **rowlock);
swTableRow* swTableRow_get(swTable *table, char *key, int keylen, sw_atomic_t **rowlock);

先来看看创建swooletable的时候会发生什么:

swTable* swTable_new(uint32_t rows_size)
{
    // 隐含限制,单个swoole table 最大128M,还是挺狠的
    if (rows_size >= 0x80000000)
    {
        rows_size = 0x80000000;
    }
    // 16进制转换,这应该也是文档里面说的,创建需要2的倍数的原因,比较好处理一些
    else
    {
        uint32_t i = 10;
        while ((1U << i) < rows_size)
        {
            i++;
        }
        rows_size = 1 << i;
    }

    // 统一申请内存
    swTable *table = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(swTable));
    if (table == NULL)
    {
        return NULL;
    }
    // 给table创建锁,独一无二
    if (swMutex_create(&table->lock, 1) < 0)
    {
        swWarn("mutex create failed.");
        return NULL;
    }
    // 预创建迭代器
    table->iterator = sw_malloc(sizeof(swTable_iterator));
    if (!table->iterator)
    {
        swWarn("malloc failed.");
        return NULL;
    }
    // 预创建存储列信息的哈希表,这里同样隐含了,最多32列的限制条件,同时制定了析构函数
    table->columns = swHashMap_new(SW_HASHMAP_INIT_BUCKET_N, (swHashMap_dtor)swTableColumn_free);
    if (!table->columns)
    {
        return NULL;
    }

    // 结构体变量初始化
    table->size = rows_size;
    table->mask = rows_size - 1;

    bzero(table->iterator, sizeof(swTable_iterator));
    table->memory = NULL;
    return table;
}

我个人比较关注关于锁的这一块,所以看了下swMutex_create方法:

int swMutex_create(swLock *lock, int use_in_process)
{
    int ret;
    bzero(lock, sizeof(swLock));
    lock->type = SW_MUTEX;
    pthread_mutexattr_init(&lock->object.mutex.attr);
    if (use_in_process == 1)
    {
        pthread_mutexattr_setpshared(&lock->object.mutex.attr, PTHREAD_PROCESS_SHARED);
    }
    if ((ret = pthread_mutex_init(&lock->object.mutex._lock, &lock->object.mutex.attr)) < 0)
    {
        return SW_ERR;
    }
    lock->lock = swMutex_lock;
    lock->unlock = swMutex_unlock;
    lock->trylock = swMutex_trylock;
    lock->free = swMutex_free;
    return SW_OK;
}

这里使用了posix thread中的用于线程同步的mutex函数来创建和初始化互斥锁。参照http://blog.sina.com.cn/s/blog_4176c2800100tabf.html 中的说明,这里swoole应该创建的是PTHREAD_MUTEX_TIMED_NP 普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

同时创建锁也给出了一个参数use_in_process, 如果是在进程间使用,那么意味着锁在进程间共享,这也就对应了swooletable的第一种使用方式:在server启动之前创建,否则就是我们上文中的使用方式:在每个进程中单独的使用。

注意,这里swoole table使用了互斥锁,这是阻塞的,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。由于table之间加锁的频率比较低,所以使用互斥锁是划算的。

再看下指定了swooletable中的列信息之后,进行swTable_create时发生了什么:

int swTable_create(swTable *table)
{
    // 数据初始化
    ...

    // 真正申请了共享内存,计算出了最终需要的大小
    void *memory = sw_shm_malloc(memory_size);
    if (memory == NULL)
    {
        return SW_ERR;
    }

    // 变量初始化
    ...
}

最后看一下我们最关注的,对于行内容的get、set、del:

先看get方法,每次get,都更新一下自旋锁

swTableRow* swTableRow_get(swTable *table, char *key, int keylen, sw_atomic_t **rowlock)
{
    //参数校验
    ...

    // 根据哈希算法获取相应的行
    swTableRow *row = swTable_hash(table, key, keylen);
    // 获取行中存储的初始的原子锁
    sw_atomic_t *lock = &row->lock;
    // 对应swSpinLock_create方法,其中调用pthread_spin_init进行自旋锁初始化
    sw_spinlock(lock);
    // 自旋锁赋值
    *rowlock = lock;

    // 遍历table,找对应的列中的行
    ...
}

再看set方法:

swTableRow* swTableRow_set(swTable *table, char *key, int keylen, sw_atomic_t **rowlock)
{
    //参数校验
    ...

    // 更新自旋锁
    swTableRow *row = swTable_hash(table, key, keylen);
    sw_atomic_t *lock = &row->lock;
    sw_spinlock(lock);
    *rowlock = lock;

    if (row->active)
    {
        for (;;)
        {
            if (strncmp(row->key, key, keylen) == 0)
            {
                break;
            }
            else if (row->next == NULL)
            {
                //!!! 锁住table
                table->lock.lock(&table->lock);
                swTableRow *new_row = table->pool->alloc(table->pool, 0);
                // !!! 创建完成,解锁table
                table->lock.unlock(&table->lock);

                if (!new_row)
                {
                    return NULL;
                }
                //add row_num
                bzero(new_row, sizeof(swTableRow));
                // 多线程全局变量自加,确保行数全局唯一,对应__sync_fetch_and_add方法!!
                sw_atomic_fetch_add(&(table->row_num), 1);
                row->next = new_row;
                row = new_row;
                break;
            }
            else
            {
                row = row->next;
            }
        }
    }
    else
    {
        // 多线程全局变量自加,确保行数全局唯一,对应__sync_fetch_and_add方法!!
        sw_atomic_fetch_add(&(table->row_num), 1);
    }

    memcpy(row->key, key, keylen);
    row->active = 1;
    return row;
}

del方法也比较类似的,这里就不讲了,仔细看看还是很有意思。核心点在于:

  • 对互斥锁、自旋锁的灵活使用
  • 对多线程下的全局变量处理
  • 对共享内存的把控与操作
  • 对内存的分配与正确回收

swoole的源码的确有很多可取之处,涉及到了很多系统和存储的基本的只是,非常值得学习。
那么,关于php使用本机存储系列,也就到此为止吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容