關(guān)于php的共享內(nèi)存的使用和研究之深入剖析swoole table

上文:
關(guān)于php的共享內(nèi)存的使用和研究之由起
關(guān)于php的共享內(nèi)存的使用和研究之外部存儲(chǔ)

話說(shuō)回來(lái)型豁,究竟swoole的底層是怎么做到了使用行鎖僵蛛,來(lái)實(shí)現(xiàn)進(jìn)程訪問(wèn)沖突解決與高性能的呢?這里確實(shí)值得研究一下迎变。

首先來(lái)看一下swooletable中用來(lái)存儲(chǔ)的基本數(shù)據(jù)結(jié)構(gòu)swTableRow:

typedef struct _swTableRow
{
    sw_atomic_t lock;// 原子鎖充尉,所謂的效率更高的行鎖,這個(gè)要等下看看了衣形。
    /**
     * 1:used, 0:empty
     */
    uint8_t active;//是否啟用狀態(tài)
    /**
     * next slot
     */
    struct _swTableRow *next;//鏈表結(jié)構(gòu)
    /**
     * Hash Key
     */
    char key[SW_TABLE_KEY_SIZE];//大小64驼侠,意味著單哈希key的長(zhǎng)度
    char data[0];//真實(shí)數(shù)據(jù)
} swTableRow;

然后是用來(lái)遍歷行的索引數(shù)據(jù)結(jié)構(gòu)swTable_iterator:

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

然后是包含了多行內(nèi)容的swTable:

typedef struct
{
    swHashMap *columns;// 一個(gè)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;// 一列包含多行谆吴,所以是個(gè)二維的數(shù)組
    swMemoryPool *pool;

    uint32_t compress_threshold;

    swTable_iterator *iterator;

    void *memory;
} swTable;

用來(lái)存儲(chǔ)swooletable中每一列信息的swTableColumn:

typedef struct
{
   uint8_t type; // 結(jié)構(gòu)類型泪电,可選是int、浮點(diǎn)纪铺、字符串
   uint32_t size; // 聲明的大小,

   swString* name;
   uint16_t index;
} swTableColumn;

// 此結(jié)構(gòu)體即為執(zhí)行$_swooleTable->column('ip',\swoole_table::TYPE_STRING, 64)時(shí)設(shè)置結(jié)構(gòu)體中內(nèi)容

幾個(gè)對(duì)外暴露的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);

先來(lái)看看創(chuàng)建swooletable的時(shí)候會(huì)發(fā)生什么:

swTable* swTable_new(uint32_t rows_size)
{
    // 隱含限制碟渺,單個(gè)swoole table 最大128M鲜锚,還是挺狠的
    if (rows_size >= 0x80000000)
    {
        rows_size = 0x80000000;
    }
    // 16進(jìn)制轉(zhuǎn)換,這應(yīng)該也是文檔里面說(shuō)的苫拍,創(chuàng)建需要2的倍數(shù)的原因芜繁,比較好處理一些
    else
    {
        uint32_t i = 10;
        while ((1U << i) < rows_size)
        {
            i++;
        }
        rows_size = 1 << i;
    }

    // 統(tǒng)一申請(qǐng)內(nèi)存
    swTable *table = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(swTable));
    if (table == NULL)
    {
        return NULL;
    }
    // 給table創(chuàng)建鎖,獨(dú)一無(wú)二
    if (swMutex_create(&table->lock, 1) < 0)
    {
        swWarn("mutex create failed.");
        return NULL;
    }
    // 預(yù)創(chuàng)建迭代器
    table->iterator = sw_malloc(sizeof(swTable_iterator));
    if (!table->iterator)
    {
        swWarn("malloc failed.");
        return NULL;
    }
    // 預(yù)創(chuàng)建存儲(chǔ)列信息的哈希表绒极,這里同樣隱含了骏令,最多32列的限制條件,同時(shí)制定了析構(gòu)函數(shù)
    table->columns = swHashMap_new(SW_HASHMAP_INIT_BUCKET_N, (swHashMap_dtor)swTableColumn_free);
    if (!table->columns)
    {
        return NULL;
    }

    // 結(jié)構(gòu)體變量初始化
    table->size = rows_size;
    table->mask = rows_size - 1;

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

我個(gè)人比較關(guān)注關(guān)于鎖的這一塊垄提,所以看了下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函數(shù)來(lái)創(chuàng)建和初始化互斥鎖榔袋。參照http://blog.sina.com.cn/s/blog_4176c2800100tabf.html 中的說(shuō)明周拐,這里swoole應(yīng)該創(chuàng)建的是PTHREAD_MUTEX_TIMED_NP 普通鎖,當(dāng)一個(gè)線程加鎖以后凰兑,其余請(qǐng)求鎖的線程將形成一個(gè)等待隊(duì)列妥粟,并在解鎖后按優(yōu)先級(jí)獲得鎖。這種鎖策略保證了資源分配的公平性吏够。

同時(shí)創(chuàng)建鎖也給出了一個(gè)參數(shù)use_in_process, 如果是在進(jìn)程間使用勾给,那么意味著鎖在進(jìn)程間共享,這也就對(duì)應(yīng)了swooletable的第一種使用方式:在server啟動(dòng)之前創(chuàng)建锅知,否則就是我們上文中的使用方式:在每個(gè)進(jìn)程中單獨(dú)的使用播急。

注意,這里swoole table使用了互斥鎖售睹,這是阻塞的桩警,當(dāng)某線程無(wú)法獲取互斥量時(shí),該線程會(huì)被直接掛起侣姆,該線程不再消耗CPU時(shí)間生真,當(dāng)其他線程釋放互斥量后,操作系統(tǒng)會(huì)激活那個(gè)被掛起的線程捺宗,讓其投入運(yùn)行柱蟀。由于table之間加鎖的頻率比較低,所以使用互斥鎖是劃算的蚜厉。

再看下指定了swooletable中的列信息之后长已,進(jìn)行swTable_create時(shí)發(fā)生了什么:

int swTable_create(swTable *table)
{
    // 數(shù)據(jù)初始化
    ...

    // 真正申請(qǐng)了共享內(nèi)存,計(jì)算出了最終需要的大小
    void *memory = sw_shm_malloc(memory_size);
    if (memory == NULL)
    {
        return SW_ERR;
    }

    // 變量初始化
    ...
}

最后看一下我們最關(guān)注的昼牛,對(duì)于行內(nèi)容的get术瓮、set、del:

先看get方法贰健,每次get胞四,都更新一下自旋鎖

swTableRow* swTableRow_get(swTable *table, char *key, int keylen, sw_atomic_t **rowlock)
{
    //參數(shù)校驗(yàn)
    ...

    // 根據(jù)哈希算法獲取相應(yīng)的行
    swTableRow *row = swTable_hash(table, key, keylen);
    // 獲取行中存儲(chǔ)的初始的原子鎖
    sw_atomic_t *lock = &row->lock;
    // 對(duì)應(yīng)swSpinLock_create方法,其中調(diào)用pthread_spin_init進(jìn)行自旋鎖初始化
    sw_spinlock(lock);
    // 自旋鎖賦值
    *rowlock = lock;

    // 遍歷table伶椿,找對(duì)應(yīng)的列中的行
    ...
}

再看set方法:

swTableRow* swTableRow_set(swTable *table, char *key, int keylen, sw_atomic_t **rowlock)
{
    //參數(shù)校驗(yàn)
    ...

    // 更新自旋鎖
    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);
                // !!! 創(chuàng)建完成辜伟,解鎖table
                table->lock.unlock(&table->lock);

                if (!new_row)
                {
                    return NULL;
                }
                //add row_num
                bzero(new_row, sizeof(swTableRow));
                // 多線程全局變量自加,確保行數(shù)全局唯一脊另,對(duì)應(yīng)__sync_fetch_and_add方法5冀啤!
                sw_atomic_fetch_add(&(table->row_num), 1);
                row->next = new_row;
                row = new_row;
                break;
            }
            else
            {
                row = row->next;
            }
        }
    }
    else
    {
        // 多線程全局變量自加偎痛,確保行數(shù)全局唯一旱捧,對(duì)應(yīng)__sync_fetch_and_add方法!踩麦!
        sw_atomic_fetch_add(&(table->row_num), 1);
    }

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

del方法也比較類似的枚赡,這里就不講了氓癌,仔細(xì)看看還是很有意思。核心點(diǎn)在于:

  • 對(duì)互斥鎖标锄、自旋鎖的靈活使用
  • 對(duì)多線程下的全局變量處理
  • 對(duì)共享內(nèi)存的把控與操作
  • 對(duì)內(nèi)存的分配與正確回收

swoole的源碼的確有很多可取之處顽铸,涉及到了很多系統(tǒng)和存儲(chǔ)的基本的只是,非常值得學(xué)習(xí)料皇。
那么谓松,關(guān)于php使用本機(jī)存儲(chǔ)系列,也就到此為止吧践剂!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鬼譬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逊脯,更是在濱河造成了極大的恐慌优质,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件军洼,死亡現(xiàn)場(chǎng)離奇詭異巩螃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)匕争,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門避乏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人甘桑,你說(shuō)我怎么就攤上這事拍皮。” “怎么了跑杭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵铆帽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我德谅,道長(zhǎng)爹橱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任窄做,我火速辦了婚禮宅荤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浸策。我一直安慰自己,他們只是感情好惹盼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布庸汗。 她就那樣靜靜地躺著,像睡著了一般手报。 火紅的嫁衣襯著肌膚如雪蚯舱。 梳的紋絲不亂的頭發(fā)上改化,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音枉昏,去河邊找鬼陈肛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兄裂,可吹牛的內(nèi)容都是我干的句旱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼晰奖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谈撒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匾南,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啃匿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蛆楞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溯乒,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年豹爹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裆悄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帅戒,死狀恐怖灯帮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逻住,我是刑警寧澤钟哥,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瞎访,受9級(jí)特大地震影響腻贰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扒秸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一播演、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伴奥,春花似錦写烤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春暂衡,著一層夾襖步出監(jiān)牢的瞬間询微,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工狂巢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撑毛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓唧领,卻偏偏與公主長(zhǎng)得像藻雌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疹吃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容