IOS中實現線程加鎖的幾個方法

轉載自: http://www.tanhao.me/tag/nslock


Objective-C中不同方式實現鎖(一)

爲什麼需要使用鎖,當然熟悉多線程的你,自然不會對它覺得陌生。

那你在代碼中是否很好的使用了鎖的機制呢?你又知道幾種實現鎖的方法呢?

今天一起來探討一下Objective-C中幾種不同方式實現的鎖,在這之前我們先構建一個測試用的類,假想它是我們的一個共享資源,method1與method2是互斥的,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation TestObj
 
- (void)method1
{
    NSLog(@"%@",NSStringFromSelector(_cmd));
}
 
- (void)method2
{
    NSLog(@"%@",NSStringFromSelector(_cmd));    
}
 
@end

1.使用NSLock實現的鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//主線程中
TestObj *obj = [[TestObj alloc] init];
NSLock *lock = [[NSLock alloc] init];
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    [obj method1];
    sleep(10);
    [lock unlock];
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);//以保證讓線程2的代碼後執行
    [lock lock];
    [obj method2];
    [lock unlock];
});

看到打印的結果了嗎,你會看到線程1鎖住之後,線程2會一直等待走到線程1將鎖置爲unlock後,纔會執行method2方法。

NSLock是Cocoa提供給我們最基本的鎖對象,這也是我們經常所使用的,除lock和unlock方法外,NSLock還提供了tryLock和lockBeforeDate:兩個方法,前一個方法會嘗試加鎖,如果鎖不可用(已經被鎖住),剛並不會阻塞線程,並返回NO。lockBeforeDate:方法會在所指定Date之前嘗試加鎖,如果在指定時間之前都不能加鎖,則返回NO。

2.使用synchronized關鍵字構建的鎖

當然在Objective-C中你還可以用@synchronized指令快速的實現鎖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//主線程中
TestObj *obj = [[TestObj alloc] init];
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    @synchronized(obj){
        [obj method1];
        sleep(10);
    }
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    @synchronized(obj){
        [obj method2];
    }
});

@synchronized指令使用的obj爲該鎖的唯一標識,只有當標識相同時,才爲滿足互斥,如果線程2中的@synchronized(obj)改爲@synchronized(other),剛線程2就不會被阻塞,@synchronized指令實現鎖的優點就是我們不需要在代碼中顯式的創建鎖對象,便可以實現鎖的機制,但作爲一種預防措施,@synchronized塊會隱式的添加一個異常處理例程來保護代碼,該處理例程會在異常拋出的時候自動的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對象。

3.使用C語言的pthread_mutex_t實現的鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//主線程中
TestObj *obj = [[TestObj alloc] init];
 
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex);
    [obj method1];
    sleep(5);
    pthread_mutex_unlock(&mutex);
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    pthread_mutex_lock(&mutex);
    [obj method2];
    pthread_mutex_unlock(&mutex);
});

pthread_mutex_t定義在pthread.h,所以記得#include <pthread.h>
4.使用GCD來實現的”鎖”
以上代碼構建多線程我們就已經用到了GCD的dispatch_async方法,其實在GCD中也已經提供了一種信號機制,使用它我們也可以來構建一把”鎖”(從本質意義上講,信號量與鎖是有區別,具體差異參加信號量與互斥鎖之間的區別):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//主線程中
TestObj *obj = [[TestObj alloc] init];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [obj method1];
    sleep(10);
    dispatch_semaphore_signal(semaphore);
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    [obj method2];
    dispatch_semaphore_signal(semaphore);
});

至於代碼產生的效果當然和上一例是一模一樣的,關於信號機制,熟悉C編程的你肯定也不會陌生的,關於GCD中更多關於dispatch_semaphore_t的信息,可以跳轉到本博客的這一往篇文章:GCD介紹(三): Dispatch Sources

好了,以上就是我所列舉了幾種方式來實現鎖,當然鎖大多數情況下也是配合多線程一起使用的,關於多線程編程,我這兒就不贅述了。

Objective-C中不同方式實現鎖(二)

在上一文中,我們已經討論過用Objective-C鎖幾種實現(跳轉地址),也用代碼實際的演示瞭如何通過構建一個互斥鎖來實現多線程的資源共享及線程安全,今天我們繼續討論鎖的一些高級用法。

1.NSRecursiveLock遞歸鎖

平時我們在代碼中使用鎖的時候,最容易犯的一個錯誤就是造成死鎖,而容易造成死鎖的一種情形就是在遞歸或循環中,如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//主線程中
NSLock *theLock = [[NSLock alloc] init];
TestObj *obj = [[TestObj alloc] init];
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
    static void(^TestMethod)(int);
    TestMethod = ^(int value)
    {
        [theLock lock];
        if (value > 0)
        {
            [obj method1];
            sleep(5);
            TestMethod(value-1);
        }
        [theLock unlock];
    };
 
    TestMethod(5);
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(1);
    [theLock lock];
    [obj method2];
    [theLock unlock];
});

以上的代碼中,就是一種典型的死鎖情況,因爲在線程1中的遞歸block中,鎖會被多次的lock,所以自己也被阻塞了,由於以上的代碼非常的簡短,所以很容易能識別死鎖,但在較爲複雜的代碼中,就不那麼容易發現了,那麼如何在遞歸或循環中正確的使用鎖呢?此處的theLock如果換用NSRecursiveLock對象,問題便得到解決了,NSRecursiveLock類定義的鎖可以在同一線程多次lock,而不會造成死鎖。遞歸鎖會跟蹤它被多少次lock。每次成功的lock都必須平衡調用unlock操作。只有所有的鎖住和解鎖操作都平衡的時候,鎖才真正被釋放給其他線程獲得。

2.NSConditionLock條件鎖

當我們在使用多線程的時候,有時一把只會lock和unlock的鎖未必就能完全滿足我們的使用。因爲普通的鎖只能關心鎖與不鎖,而不在乎用什麼鑰匙才能開鎖,而我們在處理資源共享的時候,多數情況是隻有滿足一定條件的情況下才能打開這把鎖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//主線程中
NSConditionLock *theLock = [[NSConditionLock alloc] init];
 
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i=0;i<=2;i++)
    {
        [theLock lock];
        NSLog(@"thread1:%d",i);
        sleep(2);
        [theLock unlockWithCondition:i];
    }
});
 
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [theLock lockWhenCondition:2];
    NSLog(@"thread2");
    [theLock unlock];
});

在線程1中的加鎖使用了lock,所以是不需要條件的,所以順利的就鎖住了,但在unlock的使用了一個整型的條件,它可以開啓其它線程中正在等待這把鑰匙的臨界地,而線程2則需要一把被標識爲2的鑰匙,所以當線程1循環到最後一次的時候,才最終打開了線程2中的阻塞。但即便如此,NSConditionLock也跟其它的鎖一樣,是需要lock與unlock對應的,只是lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,當然這是與你的需求相關的。

3.NSDistributedLock分佈式鎖

以上所有的鎖都是在解決多線程之間的衝突,但如果遇上多個進程或多個程序之間需要構建互斥的情景該怎麼辦呢?這個時候我們就需要使用到NSDistributedLock了,從它的類名就知道這是一個分佈式的Lock,NSDistributedLock的實現是通過文件系統的,所以使用它纔可以有效的實現不同進程之間的互斥,但NSDistributedLock並非繼承於NSLock,它沒有lock方法,它只實現了tryLock,unlock,breakLock,所以如果需要lock的話,你就必須自己實現一個tryLock的輪詢,下面通過代碼簡單的演示一下吧:

程序A:

1
2
3
4
5
6
7
8
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];
    [lock breakLock];
    [lock tryLock];
    sleep(10);
    [lock unlock];
    NSLog(@"appA: OK");
});

程序B:

1
2
3
4
5
6
7
8
9
10
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];
 
        while (![lock tryLock]) {
            NSLog(@"appB: waiting");
            sleep(1);
        }
        [lock unlock];
        NSLog(@"appB: OK");
    });

先運行程序A,然後立即運行程序B,根據打印你可以清楚的發現,當程序A剛運行的時候,程序B一直處於等待中,當大概10秒過後,程序B便打印出了appB:OK的輸出,以上便實現了兩上不同程序之間的互斥。/Users/mac/Desktop/earning__是一個文件或文件夾的地址,如果該文件或文件夾不存在,那麼在tryLock返回YES時,會自動創建該文件/文件夾。在結束的時候該文件/文件夾會被清除,所以在選擇的該路徑的時候,應該選擇一個不存在的路徑,以防止誤刪了文件。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章