iOS中常見的鎖

鎖一般用于在多線程中,保證在一段時期內(nèi)這段代碼只能被某一個線程所訪問,從而保證線程同步。在iOS中,常用的鎖大致有@synchronized,NSLock,NSCondition,NSConditionLock、NSRecursiveLock和dispatch_semaphore等,如下圖所示。


image.png
1. 常見鎖介紹

鎖按照功能分,主要可以分為互斥鎖,自旋鎖,信號量。

  • 互斥鎖禁止多個線程同時進入受保護的代碼“臨界區(qū)”(critical section)。
  • 自旋鎖跟互斥鎖一樣,但是獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖。
  • 信號量是一個計數(shù)器,它用來記錄對某個資源(如共享內(nèi)存)的存取狀況。
1.1 @synchronized

@synchronized(obj)指令使用的obj為該鎖的唯一標識,同一標識的線程互斥。這是通過一個哈希表來實現(xiàn)的,OC 在底層使用了一個互斥鎖的數(shù)組(你可以理解為鎖池),通過對對象去哈希值來得到對應的互斥鎖。@synchronized的內(nèi)部詳細原理可以參考關(guān)于 @synchronized,這兒比你想知道的還要多

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1,@2, nil];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(1);
    @synchronized(arr) {
        [arr removeLastObject];
        sleep(1);
        NSLog(@"%lu in thread1",arr.count);

    }
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    @synchronized(arr) {
        @synchronized(arr) {
            [arr removeLastObject];
            NSLog(@"%lu in thread2",arr.count);
            sleep(2);
        }
    }
});

輸出結(jié)果如下:

2017-12-20 14:55:19.646387+0800 Lock[49276:6948973] 1 in thread2
2017-12-20 14:55:22.653366+0800 Lock[49276:6948972] 0 in thread1
1.2 NSConditon

NSConditon是一種條件鎖。當其他線程的鎖收到signal(單發(fā))或者broadcast(多發(fā))時,如果條件為true,則會上鎖。

NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lock];
    while (!array.count) {
        [lock wait];
    }
    [array removeLastObject];
    NSLog(@"remove last object %@",array);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lock];
    while (!array.count) {
        [lock wait];
    }
    [array removeLastObject];
    NSLog(@"remove last object %@",array);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(1);
    [lock lock];
    [array addObjectsFromArray:@[@1,@2]];
    NSLog(@"add objects @1,@2");
    [lock broadcast];
    [lock unlock];
});
}

輸出如下:

2017-12-20 15:09:27.322078+0800 Lock[67589:7008671] add objects @1,@2
2017-12-20 15:09:27.322539+0800 Lock[67589:7008668] remove last object (
1
)
2017-12-20 15:09:27.322731+0800 Lock[67589:7008669] remove last object (
)
1.3 NSConditionLock

NSConditionLock這種條件鎖在初始化時可以指定一個條件,在上鎖時,只有滿足這個條件才能上鎖,在解鎖時,可以修改這個條件鎖的條件。

NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:1];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [lock lockWhenCondition:0];
    NSLog(@"locked this lock in thread 1");
    sleep(1);
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(2);
    if ([lock tryLockWhenCondition:1]) {
        NSLog(@"locked this lock in thread 2");
        sleep(1);
        [lock unlockWithCondition:0];
        NSLog(@"unlocked this lock in thread 2");
    } else {
        NSLog(@"fail locked this lock in thread 2");
    }
});

輸出如下:

2017-12-20 15:16:32.725752+0800 Lock[76697:7037383] locked this lock in thread 2
2017-12-20 15:16:33.726779+0800 Lock[76697:7037383] unlocked this lock in thread 2
2017-12-20 15:16:33.726803+0800 Lock[76697:7037384] locked this lock in thread 1
1.4 NSRecursiveLock

遞歸鎖用在需要遞歸的地方。這種鎖不會因為重復上鎖而導致死鎖。

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    static void (^recursiveBlock)(int);
    recursiveBlock = ^(int value) {
        [lock lock];
        if (value > 0) {
            NSLog(@"value is %d",value);
            recursiveBlock(value-1);
        }
        [lock unlock];
    };
    recursiveBlock(5);
});

輸出如下:

2017-12-20 15:21:09.465255+0800 Lock[82850:7057130] value is 5
2017-12-20 15:21:09.465562+0800 Lock[82850:7057130] value is 4
2017-12-20 15:21:09.465739+0800 Lock[82850:7057130] value is 3
2017-12-20 15:21:09.465906+0800 Lock[82850:7057130] value is 2
2017-12-20 15:21:09.466064+0800 Lock[82850:7057130] value is 1
1.5 dispatch_semaphore

使用方法非常簡單,dispatch_semaphore_create(1)為創(chuàng)建信號,數(shù)字表示可以同時幾個線程使用信號。為1表示同步使用。如果此處標2就和沒設(shè)置信號量一樣,并發(fā)自行運行。如果設(shè)置為0,則一律等待overTime時自動釋放,所有代碼都不執(zhí)行,理論上也具有同步作用,就是慢點…

dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"線程1 等待ing");
    dispatch_semaphore_wait(signal, timeout); //signal 值 -1
    NSLog(@"線程1 sleep");
    sleep(2);
    NSLog(@"線程1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"線程1 發(fā)送信號");
});

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"線程2 等待ing");
    dispatch_semaphore_wait(signal, timeout);
    NSLog(@"線程2 sleep");
    sleep(2);
    NSLog(@"線程2");
    dispatch_semaphore_signal(signal);
    NSLog(@"線程2 發(fā)送信號");
});

輸出如下:

2017-12-20 16:28:19.286668+0800 Lock[70914:7323787] 線程2 等待ing
2017-12-20 16:28:19.286670+0800 Lock[70914:7323790] 線程1 等待ing
2017-12-20 16:28:19.286810+0800 Lock[70914:7323787] 線程2 sleep
2017-12-20 16:28:21.290357+0800 Lock[70914:7323787] 線程2
2017-12-20 16:28:21.290556+0800 Lock[70914:7323787] 線程2 發(fā)送信號
2017-12-20 16:28:21.290566+0800 Lock[70914:7323790] 線程1 sleep
2017-12-20 16:28:23.294412+0800 Lock[70914:7323790] 線程1
2017-12-20 16:28:23.294572+0800 Lock[70914:7323790] 線程1 發(fā)送信號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 在平時的開發(fā)中經(jīng)常使用到多線程,在使用多線程的過程中,難免會遇到資源競爭的問題,那我們怎么來避免出現(xiàn)這種問題那? ...
    IAMCJ閱讀 3,135評論 2 25
  • 鎖是一種同步機制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,558評論 0 6
  • 前言 iOS開發(fā)中由于各種第三方庫的高度封裝,對鎖的使用很少,剛好之前面試中被問到的關(guān)于并發(fā)編程鎖的問題,都是一知...
    喵渣渣閱讀 3,732評論 0 33
  • 前言 在多線程開發(fā)中,常會遇到多個線程訪問修改數(shù)據(jù)。為了防止數(shù)據(jù)不一致或數(shù)據(jù)污染,通常采用加鎖機制來保證線程安全。...
    趙夢楠閱讀 1,007評論 0 5
  • 前言 一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,比如多個線程訪問同一個對象、同一個變量、同...
    WQ_UESTC閱讀 896評論 0 5