各種鎖介紹以及性能對比

  • 多線程為我們帶來了很大便利,也提高了程序的執行效率,但同時也帶來了Data race(當至少有兩個線程同時訪問同一個變量,而且至少其中有一個是寫操作時,就發生了Data race)。這時就要利用一些同步機制來確保數據的準確性,就是同步機制中的一種。

一、各種鎖

  • @synchronized 關鍵字加鎖
  • NSLock 對象鎖
  • NSCondition 條件鎖1
  • NSConditionLock 條件鎖2
  • NSRecursiveLock 遞歸鎖
  • pthread_mutex 互斥鎖(C語言)
  • pthread_mutex(recursive) 互斥鎖2(遞歸鎖一種類似NSConditionLock)
  • dispath_semaphore 信號量實現加鎖(GCD)
  • OSSpinlock 自旋鎖(iOS10以后被廢棄,因其不安全,有可能造成死鎖)
  • os_unfair_lock 自旋鎖(iOS之后才可以使用,代替 OSSpinlock的方案)

二、性能比對圖

lock_benchmark.png

性能測試Demo的GitHub地址:https://github.com/Yjunjie/MultithreadingAndLock/tree/master
參考鏈接:http://www.lxweimin.com/p/c9c5bc68449d

  • 總體來說:
    OSSpinLockdispatch_semaphore的效率遠遠高于其他。
    @synchronizedNSConditionLock效率較差。

  • 臨界區指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。

三、詳細介紹

  • 自旋鎖:
    如果共享數據被其他線程加鎖,那么當前線程會以死循環的方式等待解鎖,一旦訪問的資源被解鎖,則等待線程就會立即執行。自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的。

1、OSSpinLock(不安全,已廢棄)
可能造成死鎖的原因:
有可能在優先級比較低的線程里對共享資源進行加鎖了,然后高優先級的線程搶占了低優先級的調用CPU時間,導致高優先級的線程一直在等待低優先級的線程釋放鎖,然而低優先級根本沒法搶占高優先級的CPU時間。
這種情況我們稱作 優先級倒轉。

OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
...
OSSpinLockUnlock(&lock);

2、os_unfair_lock
os_unfair_lock 是蘋果官方推薦的替換OSSpinLock的方案,但是它在iOS10.0以上的系統才可以調用。

os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
  • 互斥鎖(Mutex):
    如果共享資源已經有其他線程加鎖了,線程會進入休眠狀態等待鎖。一旦被訪問的資源被解鎖,則等待資源的線程會被喚醒。
    互斥鎖不會同時被兩個不同的線程同時得到。也就是如果是當前線程加的鎖,別的線程是沒有辦法獲取這個鎖,也就沒有辦法對他進行解鎖。

1、NSLock
Foundation框架中以對象形式暴露給開發者的一種鎖,在AFNetworking的AFURLSessionManager.m中應用如下:

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    ...
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;
    ...
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    ...
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

2、pthread_mutex
實際項目中: 在YYKit的YYMemoryCach中可以看到

- (instancetype)init {
    ...
    pthread_mutex_init(&_lock, NULL);
    ...
}
- (void)_trimToCost:(NSUInteger)costLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (costLimit == 0) {
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCost <= costLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;

    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
   ...
}

3、@synchronized:
實際項目中:AFNetworking中 isNetworkActivityOccurring屬性的getter方法

- (BOOL)isNetworkActivityOccurring {
    @synchronized(self) {
        return self.activityCount > 0;
    }
}
  • 條件鎖:
    當進程的某些資源要求不滿足時就進入休眠等待,也就是鎖住了。直到滿足條件后,條件鎖打開,進程繼續運行。

1、NSCondition

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

遵循NSLocking協議,使用的時候同樣是lock,unlock加解鎖,wait是傻等,waitUntilDate:方法是等一會,都會阻塞線程,signal是喚起一個在等待的線程,broadcast是廣播全部喚起。

NSCondition *lock = [[NSCondition alloc] init];
//Son 線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    while (No Money) {
        [lock wait];
    }
    NSLog(@"The money has been used up.");
    [lock unlock];
});

 //Father線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    NSLog(@"Work hard to make money.");
    [lock signal];
    [lock unlock];
 });

2、NSConditionLock

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
  • 遞歸鎖
    特點:同一個線程可以加鎖N次而不會死鎖。

1、NSRecursiveLock:
NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到:

_lock = [NSRecursiveLock new];
- (void)dealloc {
    [_lock lock];
    ...
    ...
    [_lock unlock];
}

2、pthread_mutex(recursive)
pthread_mutex鎖也支持遞歸,只需要設置PTHREAD_MUTEX_RECURSIVE即可

pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
  • 信號量加鎖
    多元信號量允許多個線程訪問同一個資源,多元信號量簡稱信號量(Semaphore),對于允許多個線程并發訪問的資源,這是一個很好的選擇。一個初始值為N的信號量允許N個線程并發訪問。其實嚴格的來說信號量不能算鎖。而且如果信號量設置為1,我們可以把它當作互斥鎖來用

1、dispatch_semaphore
dispatch_semaphore在YYKit中的YYThreadSafeArray.m有所應用,YY大神有這樣一句注釋:

@discussion Generally, access performance is lower than NSMutableArray, 
 but higher than using @synchronized, NSLock, or pthread_mutex_t.
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);

補充:讀寫鎖(又稱共享-互斥鎖)

允許多個線程同時對同一個數據進行讀操作,而只允許一個線程進行寫操作。這是因為讀操作不會改變數據的內容,是安全的;而寫操作會改變數據的內容,是不安全的。

1、pthread_rwlock_t

//加讀鎖
pthread_rwlock_rdlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
//加寫鎖
pthread_rwlock_wrlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容