目錄
一、linux鎖的種類
- 讀寫鎖
-
以下內容摘自《Linux中的各種鎖及其基本原理》
-
讀寫鎖也叫共享互斥鎖:讀模式共享和寫模式互斥,本質上這種非常合理,因爲在數據沒有被寫的前提下,多個使用者讀取時完全不需要加鎖的。讀寫鎖有讀加鎖狀態、寫加鎖狀態和不加鎖狀態三種狀態,當讀寫鎖在寫加鎖模式下,任何試圖對這個鎖進行加鎖的線程都會被阻塞,直到寫進程對其解鎖。
讀優先的讀寫鎖:讀寫鎖rwlock默認的也是讀優先,也就是:當讀寫鎖在讀加鎖模式先,任何線程都可以對其進行讀加鎖操作,但是所有試圖進行寫加鎖操作的線程都會被阻塞,直到所有的讀線程都解鎖,因此讀寫鎖很適合讀次數遠遠大於寫的情況。這種情況需要考慮寫飢餓問題,也就是大量的讀一直輪不到寫,因此需要設置公平的讀寫策略。在一次面試中曾經問到實現一個寫優先級的讀寫鎖,感興趣的可以想想如何實現。
-
- 自旋鎖
- 類似try鎖,不停的去嘗試解鎖,需要手動的usleep以下,有網友測試,usleep 1ms or 10ms性能最高,看實際場景吧
- 互斥鎖
- 無鎖時,阻塞
- RCU鎖
- 以下內容摘自《Linux中的各種鎖及其基本原理》:
- RCU鎖是讀寫鎖的擴展版本,簡單來說就是支持多讀多寫同時加鎖,多讀沒什麼好說的,但是對於多寫同時加鎖,還是存在一些技術挑戰的。RCU鎖翻譯爲Read Copy Update Lock,讀-拷貝-更新 鎖。Copy拷貝:寫者在訪問臨界區時,寫者將先拷貝一個臨界區副本,然後對副本進行修改;Update更新:RCU機制將在在適當時機使用一個回調函數把指向原來臨界區的指針重新指向新的被修改的臨界區,鎖機制中的垃圾收集器負責回調函數的調用。更新時機:沒有CPU再去操作這段被RCU保護的臨界區後,這段臨界區即可回收了,此時回調函數即被調用。從實現邏輯來看,RCU鎖在多個寫者之間的同步開銷還是比較大的,涉及到多份數據拷貝,回調函數等,因此這種鎖機制的使用範圍比較窄,適用於讀多寫少的情況,如網絡路由表的查詢更新、設備狀態表更新等,在業務開發中使用不是很多。
- 可重入鎖和不可重入鎖
- 遞歸鎖recursive mutex 可重入鎖(reentrant mutex)
- 非遞歸鎖non-recursive mutex 不可重入鎖(non-reentrant mutex)
- Tips:
- Linux下的pthread_mutex_t鎖默認是非遞歸的。在Linux中可以顯式設置PTHREAD_MUTEX_RECURSIVE屬性,將pthread_mutex_t設爲遞歸鎖避免這種場景。
二、無鎖操作
gcc bulid-in的一些API:
原子操作:
type __sync_fetch_and_add (type *ptr, type value, ...);
type __sync_fetch_and_sub (type *ptr, type value, ...);
type __sync_fetch_and_or (type *ptr, type value, ...);
type __sync_fetch_and_and (type *ptr, type value, ...);
type __sync_fetch_and_xor (type *ptr, type value, ...);
type __sync_fetch_and_nand (type *ptr, type value, ...);
type __sync_add_and_fetch (type *ptr, type value, ...);
type __sync_sub_and_fetch (type *ptr, type value, ...);
type __sync_or_and_fetch (type *ptr, type value, ...);
type __sync_and_and_fetch (type *ptr, type value, ...);
type __sync_xor_and_fetch (type *ptr, type value, ...);
type __sync_nand_and_fetch (type *ptr, type value, ...);
type可以是如下類型:
__sync_fetch_and_add,速度是線程鎖的6~7倍
type可以是1,2,4或者8字節長度的int類型,即:
int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
int64_t
uint64_t
關於type不需要特別聲明,直接使用即可
樣例代碼如下:
#include <stdio.h>
int main(int argc, char **argv)
{
int n=10;
__sync_fetch_and_add(&n, 1);
printf("++%d\n",n);
__sync_fetch_and_sub(&n, 1);
printf("--%d\n",n);
__sync_fetch_and_xor(&n, 1);
printf("m^n:%d\n",n);
return 0;
}
結果如下:
++11
--10
m^n:11
三、無鎖化編程
//以下兩個函數提供原子的比較和交換, 如果*ptr = oldValue, 就將newValue寫入*ptr
//第一個函數在相等並寫入的情況下返回true
//第二個函數返回操作之前的值
bool __sync_bool_compare_and_swap(type* ptr, type oldValue, type newValue, ....);
type __sync_val_compare_and_swap(type* ptr, type oldValue, type newValue, ....);
//將*ptr設爲value並返回*ptr操作之前的值
type __sync_lock_test_and_set(type *ptr, type value, ....);
//置*ptr爲0
void __sync_lock_release(type* ptr, ....);
PHP的自旋鎖,使用了 __sync_bool_compare_and_swap
四、部分原子操作源碼
以上圖片轉自:https://www.zhihu.com/question/280022939
五、參考資料:
1.
gcc中內置的原子操作__sync_fetch_and_add的實現在哪個文件裏?
https://www.zhihu.com/question/280022939
https://www.cnblogs.com/TMesh/p/11730847.html
3.Linux 自旋鎖介紹:
https://www.cnblogs.com/chenpingzhao/archive/2015/12/13/5043746.html