關于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閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容