摘要:受限于的實現,程序無法使用多線程進行編程開發。比如實現一個聊天室程序,用戶在進程中處理,用戶在進程中處理,和如果在同一個,這個在多線程環境中直接用表示,和加到對應的中即可。想要解決這個問題,必須實現一個基于共享內存的數據結構。
Swoole項目從 2012 年推出到現在已經有 5 年的歷史,現在越來越多的互聯網企業使用Swoole來開發各類后臺應用。受限于 PHP 的ZendVM實現,PHP 程序無法使用多線程進行編程開發。應用程序中實現并行處理只能使用多進程模式。
做過多進程開發的 PHPer 都知道進程的內存隔離性。在程序中聲明的global全局數組,實際上并不是數據共享的,在一個進程內修改數組的值,在另外一個進程中是無效的。
$array = array(); function process1() { global $array; $array["test"] = "hello world"; } function process2() { global $array; //這里讀取不到test的值 var_dump($array["test"]); }
這個進程隔離性給程序的開發帶來的很多煩惱。比如實現一個聊天室程序,用戶A在進程1中處理,用戶B在進程2中處理,A和B如果在同一個group,這個group在多線程環境中直接用set表示,A和B加到對應group的set中即可。但多進程環境中,用 PHP 的array無法實現。一般可以有2個思路解決問題:
進程間通信,可以使用管道,向另外一個進程發送請求,獲取數據的值
借助存儲實現,如Redis、MySQL、文件
這2個方案雖然可以實現,但都存在明顯的缺點。方案一實現較為復雜,開發困難。方案二實現簡單,但存在額外的IO消耗,不是純內存操作,有性能瓶頸。基于/dev/shm實現內存文件讀寫的方案,是一個不錯的方案,但需要注意鎖的操作,讀寫時需要額外的系統調用開銷。
想要解決這個問題,必須實現一個基于共享內存的數據結構。在 PHP 中也有一些擴展模塊可以使用。如APCu、Yac、shm_put_var/shm_get_var
Yac:性能高,但由于底層實現的限制,無法保證一致性。只能作為Cache來使用
APCu:支持Key-Value式數據的讀寫,缺點是實現簡單粗暴,鎖的粒度太粗。高并發時存在大量鎖的爭搶,性能較差
shm 系列函數:這個方案雖然能實現共享內存操作,但實際上底層實現非常簡陋。一方面底層根本沒有加鎖,如果你要在并發環境中使用,需要自行實現鎖的操作。另外,底層實際上是一個鏈表結構,數據較多時,查詢性能非常差
swoole_table 介紹為了解決多進程程序中數據共享的難題,Swoole擴展提供了swoole_table數據結構。Table的實現非常精巧,使用最方便,同時性能也是最好的。
$table = new swoole_table(1024); $table->column("id", swoole_table::TYPE_INT, 4); $table->column("name", swoole_table::TYPE_STRING, 64); $table->column("num", swoole_table::TYPE_FLOAT); $table->create(); $table->set("tianfenghan@qq.com", array("id" => 145, "name" => "rango", "num" => 3.1415)); $table->set("350749960@qq.com", array("id" => 358, "name" => "Rango1234", "num" => 3.1415)); $table->set("hello@qq.com", array("id" => 189, "name" => "rango3", "num" => 3.1415)); $ret1 = $table->get("350749960@qq.com"); $ret2 = $table->get("tianfenghan@qq.com"); $table->del("350749960@qq.com");
Table實現了一個二維Map結構,有點像 PHP 的二維數組,簡單易用。在最新的1.9.19中還可以使用ArrayAccess接口以array的方式操作Table:
$table = new swoole_table(1024); $table->column("id", swoole_table::TYPE_INT); $table->column("name", swoole_table::TYPE_STRING, 64); $table->column("num", swoole_table::TYPE_FLOAT); $table->create(); $table["apple"] = array("id" => 145, "name" => "iPhone", "num" => 3.1415); $table["google"] = array("id" => 358, "name" => "AlphaGo", "num" => 3.1415); $table["microsoft"]["name"] = "Windows"; $table["microsoft"]["num"] = "1997.03"; var_dump($table["apple"]); var_dump($table["microsoft"]); $table["google"]["num"] = 500.90; var_dump($table["google"]);Table的優勢
性能極高,全部是純內存操作,沒有任何系統調用和IO的開銷。在酷睿I5機器上測試,Table單進程單線程每秒可完成寫操作300萬次,讀操作每秒可完成150萬次。在24核服務器上,理論上每秒可實現數千萬次讀寫操作。
使用數據行鎖,底層使用了數據行鎖自旋鎖。多進程并發執行時,讀寫不同的key不存在鎖的爭搶問題。只有同一CPU時間讀寫同一個Key才需要進行加鎖操作。而且Table本身鎖的粒度非常小,get、set操作內部只有少量內存讀寫的指令,可以在數百納秒內完成操作。
Table的局限性Key最大長度不得超過64字節
必須在創建前規劃好容量,一旦寫滿后,再set新的數據會出現內存分配導致失敗,無法實現動態擴容
因此使用Table時盡可能地設置較大的內存尺寸,這樣雖然會帶來一定的內存浪費,但實際上現代服務器內存非常廉價,這個局限性在實際項目中的問題并不大。
swoole_table 實現原理Table底層基于共享內存實現,所占內存取決于表格的尺寸size、沖突率(默認20%)、column的設置(如上面的示例中每行需要8 + 64 + 8字節)、64字節KEY的存儲空間、管理結構的內存消耗。
Table 的內存申請size_t row_num = table->size * (1 + table->conflict_proportion); size_t row_memory_size = sizeof(swTableRow) + table->item_size; size_t memory_size = row_num * row_memory_size; memory_size += sizeof(swMemoryPool) + sizeof(swFixedPool) + ((row_num - table->size) * sizeof(swFixedPool_slice)); memory_size += table->size * sizeof(swTableRow *); void *memory = sw_shm_malloc(memory_size);
swoole_table本身是一個HashTable結構,Key會計算為hash值,來散列到每一行。HashTable結構會遇到Hash沖突問題,兩個完全不同的Key可能計算的hash值是同一個,這時需要使用鏈表來解決Hash沖突。Swoole底層會創建一個浮動的內存池swFixedPool結構來管理這些沖突Key的內存。默認會創建size * 20%數量的浮動內存池。在1.9.19中可以自行定義沖突率。
$table = new swoole_table(65536, 0.9);
假如你的場景中Hash沖突較多,可以調高沖突率,以申請一塊較大的浮動內存池。
static swTableRow* swTable_hash(swTable *table, char *key, int keylen) { #ifdef SW_TABLE_USE_PHP_HASH uint64_t hashv = swoole_hash_php(key, keylen); #else uint64_t hashv = swoole_hash_austin(key, keylen); #endif uint64_t index = hashv & table->mask; assert(index < table->size); return table->rows[index]; } swTableRow* swTableRow_set(swTable *table, char *key, int keylen, swTableRow **rowlock) { if (keylen > SW_TABLE_KEY_SIZE) { keylen = SW_TABLE_KEY_SIZE; } swTableRow *row = swTable_hash(table, key, keylen); *rowlock = row; swTableRow_lock(row); #ifdef SW_TABLE_DEBUG int _conflict_level = 0; #endif if (row->active) { for (;;) { if (strncmp(row->key, key, keylen) == 0) { break; } else if (row->next == NULL) { table->lock.lock(&table->lock); swTableRow *new_row = table->pool->alloc(table->pool, 0); #ifdef SW_TABLE_DEBUG conflict_count ++; if (_conflict_level > conflict_max_level) { conflict_max_level = _conflict_level; } #endif table->lock.unlock(&table->lock); if (!new_row) { return NULL; } //add row_num bzero(new_row, sizeof(swTableRow)); sw_atomic_fetch_add(&(table->row_num), 1); row->next = new_row; row = new_row; break; } else { row = row->next; #ifdef SW_TABLE_DEBUG _conflict_level++; #endif } } } else { #ifdef SW_TABLE_DEBUG insert_count ++; #endif sw_atomic_fetch_add(&(table->row_num), 1); } memcpy(row->key, key, keylen); row->active = 1; return row; }
使用swTable_hash計算hash值,散列到對應的行
Key發生沖突時,需要調用table->pool->alloc從浮動內存池中分配內存
浮動內存池內存不足時,alloc失敗,這時無法寫入數據到Table
數據自旋鎖當同一CPU時間,多個進程同時讀取某一行時,需要鎖的爭搶。
swTableRow_lock(row); //內存操作 swTableRow_unlock(_rowlock);
swTableRow_lock 本身是一個自選鎖,這里使用了gcc編譯器提供的__sync_bool_compare_and_swap函數進行CPU原子操作。多個進程同時讀寫某一行數據時,先得到鎖的進程會執行內存讀寫操作,未得到鎖的進程會進行CPU自旋等待進程釋放鎖。
static sw_inline void sw_spinlock(sw_atomic_t *lock) { uint32_t i, n; while (1) { if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { return; } if (SW_CPU_NUM > 1) { for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1) { for (i = 0; i < n; i++) { sw_atomic_cpu_pause(); } if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { return; } } } swYield(); } }返回結果
使用table::get方法時,從Table共享內存中,讀取數據寫入到PHP本地內存數組中。底層會根據列信息table->columns,計算內存指針的偏移量,得到對應字段的值。
static inline void php_swoole_table_row2array(swTable *table, swTableRow *row, zval *return_value) { array_init(return_value); swTableColumn *col = NULL; swTable_string_length_t vlen = 0; double dval = 0; int64_t lval = 0; char *k; while(1) { col = swHashMap_each(table->columns, &k); if (col == NULL) { break; } if (col->type == SW_TABLE_STRING) { memcpy(&vlen, row->data + col->index, sizeof(swTable_string_length_t)); sw_add_assoc_stringl_ex(return_value, col->name->str, col->name->length + 1, row->data + col->index + sizeof(swTable_string_length_t), vlen, 1); } else if (col->type == SW_TABLE_FLOAT) { memcpy(&dval, row->data + col->index, sizeof(dval)); sw_add_assoc_double_ex(return_value, col->name->str, col->name->length + 1, dval); } else { switch (col->type) { case SW_TABLE_INT8: memcpy(&lval, row->data + col->index, 1); sw_add_assoc_long_ex(return_value, col->name->str, col->name->length + 1, (int8_t) lval); break; case SW_TABLE_INT16: memcpy(&lval, row->data + col->index, 2); sw_add_assoc_long_ex(return_value, col->name->str, col->name->length + 1, (int16_t) lval); break; case SW_TABLE_INT32: memcpy(&lval, row->data + col->index, 4); sw_add_assoc_long_ex(return_value, col->name->str, col->name->length + 1, (int32_t) lval); break; default: memcpy(&lval, row->data + col->index, 8); sw_add_assoc_long_ex(return_value, col->name->str, col->name->length + 1, lval); break; } } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25728.html
摘要:從入門到放棄三一進程子進程創建成功后要執行的函數重定向子進程的標準輸入和輸出。默認為阻塞讀取。是否創建管道,啟用后,此選項將忽略用戶參數,強制為。 swoole——從入門到放棄(三) 一、進程 swoole_process SwooleProcess swoole_process::__construct(callable $function, $redirect_stdin...
摘要:從入門到放棄三一進程子進程創建成功后要執行的函數重定向子進程的標準輸入和輸出。默認為阻塞讀取。是否創建管道,啟用后,此選項將忽略用戶參數,強制為。 swoole——從入門到放棄(三) 一、進程 swoole_process SwooleProcess swoole_process::__construct(callable $function, $redirect_stdin...
摘要:如果互斥鎖的持有者死亡了,或者持有這樣的互斥鎖的進程了互斥鎖所在的共享內存或者持有這樣的互斥鎖的進程執行了調用,則會解除鎖定該互斥鎖。互斥鎖的下一個持有者將獲取該互斥鎖并返回錯誤。 前言 swoole_table 一個基于共享內存和鎖實現的超高性能,并發數據結構。用于解決多進程/多線程數據共享和同步加鎖問題。 swoole_table 的數據結構 swoole_table 實際上...
摘要:在中的應用官網源碼解讀號外號外歡迎大家我們開發組定了一個就線下聚一次的小目標上一篇源碼解讀反響還不錯不少同學推薦再加一篇講解一下中使用到的功能幫助大家開啟的實戰之旅服務器開發涉及到的相關技術領域的知識非常多不日積月累打好基礎是很難真正 date: 2017-12-14 21:34:51title: swoole 在 swoft 中的應用 swoft 官網: https://www.sw...
閱讀 3010·2021-10-12 10:12
閱讀 3060·2021-09-22 16:04
閱讀 3294·2019-08-30 15:54
閱讀 2607·2019-08-29 16:59
閱讀 2914·2019-08-29 16:08
閱讀 874·2019-08-29 11:20
閱讀 3497·2019-08-28 18:08
閱讀 653·2019-08-26 13:43