上文:
關(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ǔ)系列,也就到此為止吧践剂!