上文:
關于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使用本機存儲系列,也就到此為止吧!