前言
上一篇文章重點(diǎn)講解了@synchronized的使用以及其底層原理,其實(shí)iOS開發(fā)中還提供了其他鎖讓我們使用,那么現(xiàn)在就開始來分析探索各種所的使用。
準(zhǔn)備工作
1. NSLock和NSRecursiveLock的使用
首先我們引入下面的案例:
- (void)lg_testRecursive{
for (int i= 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
};
testMethod(10);
});
}
}
運(yùn)行結(jié)果如下:

其中
testMethod為靜態(tài)block,因?yàn)?code>多線程的影響,所以運(yùn)行結(jié)果錯(cuò)亂無序的,但是這個(gè)案例還是出現(xiàn)了混亂的過程,這是什么回事呢?又有什么解決的辦法呢?請(qǐng)繼續(xù)往下。
加載遞歸里面:

在分析
@synchronized時(shí)知道,數(shù)據(jù)結(jié)構(gòu)SyncData中分裝了recursive_mutex_t遞歸互斥鎖,@synchronized是一個(gè)支持多線程遞歸的鎖。
1.1 NSLock
使用NSLock進(jìn)行加鎖,將鎖加在業(yè)務(wù)代碼外層,修改后代碼如下:

好明顯 在業(yè)務(wù)代碼
外層使用NSLock是可以的,那么在業(yè)務(wù)代碼中使用NSLock加鎖呢?請(qǐng)往下看:
此時(shí)的輸出被阻塞了,這是為什么呢?因?yàn)樵谡{(diào)用
textMethod方法之后,lock加鎖,內(nèi)部又繼續(xù)調(diào)用testMethod,導(dǎo)致重復(fù)加鎖。
注意:這里說明NSLock是不支持遞歸加鎖!
1.2 NSRecursiveLock
同樣的流程,在業(yè)務(wù)代碼外部使用NSRecursiveLock進(jìn)行加鎖,如下:

由運(yùn)行結(jié)果可以看出,使用
NSRecursiveLock在業(yè)務(wù)代碼外層加鎖是可以的,那么放在業(yè)務(wù)代碼內(nèi),請(qǐng)往下看:
通過上面的運(yùn)行結(jié)果發(fā)現(xiàn),
NSRecursiveLock在完成一次業(yè)務(wù)操作后就崩潰了。
注意:以上說明NSRecursiveLock支持單線程內(nèi)的遞歸加鎖,但是并不支持多線程遞歸。
2. NSCondition的使用
NSCondition的對(duì)象實(shí)際上作為?個(gè)鎖和?個(gè)線程檢查器:鎖主要為了當(dāng)檢測(cè)條件時(shí)保護(hù)數(shù)據(jù)源,執(zhí)?條件引發(fā)的任務(wù);線程檢查器主要是根據(jù)條件決定是否繼續(xù)運(yùn)?線程,即線程是否被阻塞。
2.1 NSCondition提供的API
-
[condition lock];?般?于多線程同時(shí)訪問、修改同?個(gè)數(shù)據(jù)源,保證在同?時(shí)間內(nèi)數(shù)據(jù)源只被訪問、修改?次,其他線程的命令需要在lock外等待,只到unlock,才可訪問。 -
[condition unlock];與lock同時(shí)使?。 -
[condition wait];讓當(dāng)前線程處于等待狀態(tài)。 -
[condition signal];CPU發(fā)信號(hào)告訴線程不?在等待,可以繼續(xù)執(zhí)?。
2.2 案例分析
模擬生產(chǎn)和消費(fèi)的需求,開啟多個(gè)線程進(jìn)行產(chǎn)品生產(chǎn),同時(shí)開啟多個(gè)線程進(jìn)行銷售產(chǎn)品。見下面案例:
#pragma mark **-- NSCondition**
- (void)lg_testConditon{
NSCondition *testCondition = [[NSCondition alloc] init];
// 創(chuàng)建-生產(chǎn)者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
// 創(chuàng)建-消費(fèi)者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
}
}
- (void)lg_producer{
[_testCondition lock]; // 操作的多線程影響
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal]; // 信號(hào)
[_testCondition unlock];
}
- (void) lg_consumer {
[_testCondition lock]; // 操作的多線程影響
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
// 注意消費(fèi)行為,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
注意:
生產(chǎn)線、消費(fèi)線需要進(jìn)行加鎖處理,以保證多線程安全,于此同時(shí),生產(chǎn)和消費(fèi)之前也存在關(guān)系,比如庫存數(shù)的安全!通過[_testCondition wait];模擬庫存不足,讓消費(fèi)窗口停止消費(fèi);用[_testCondition signal];模擬已有庫存可以消費(fèi),向等待的線程發(fā)送信號(hào),開始執(zhí)行。
運(yùn)行結(jié)果如下:

3. Foundation源碼了解鎖的封裝
NSLock、NSCondtion、NSRecursiveLock底層都是對(duì)pthread的封裝。它們的底層實(shí)現(xiàn)都封裝在了OC環(huán)境下的Foundation框架中,由于Foundation框架不開源,那么我們只能從它的聲明部分進(jìn)行簡(jiǎn)單的分析探索。


那么看了上面的生面,可以知道定義了一個(gè)
NSLocking協(xié)議,并且提供了lock和unlock方法。所以我們使用的?如條件鎖,遞歸鎖都會(huì)有對(duì)應(yīng)的lock方法和unlock方法。
-
NSLock的底層分析
源碼中全局搜索NSLock:,查到NSLock鎖定義的地方,見下圖:
NSLock定義
然后進(jìn)入定義中的pthread_mutex_init函數(shù),如下:
pthread_mutex_init
得出:
lock方法中,調(diào)用了pthread_mutex_lock函數(shù);unlock方法中,調(diào)用了pthread_mutex_unlock函數(shù)。底層就是對(duì)pthread的封裝。 -
NSRecursiveLock的底層分析
采用相同的方式,搜索遞歸鎖NSRecursiveLock,如下:
NSRecursiveLock定義
然后進(jìn)入pthread_mutex_init方法,都是一樣的。如下:
pthread_mutex_init
得出:
發(fā)現(xiàn)其也是對(duì)pthread的封裝,并且通過在init方法中,通過pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))進(jìn)行遞歸設(shè)置。 -
NSCondition的底層分析
NSCondition底層也是對(duì)pthread進(jìn)行了封裝,見下圖:
NSCondition定義
NSCondition其他操作
得出:
除了進(jìn)行pthread_mutex互斥處理外,還對(duì)pthread_cond進(jìn)行了處理,同時(shí)提供了wait、signal、broadcase方法。底層同樣對(duì)phread的封裝。 -
NSConditionLock的底層分析
NSConditionLock底層沒有直接操作pthread_mutex,如下:
NSConditionLock定義
但是NSConditionLock實(shí)現(xiàn)中提供了一個(gè)NSCondition屬性和一個(gè)pthread_t屬性,通過這兩個(gè)屬性,實(shí)現(xiàn)加鎖和線程方面的相關(guān)處理。所以也可以推斷出NSConditionLock也是由phread封裝得來的。
4. NSConditionLock的使用
NSConditionLock也是一種條件鎖,?旦?個(gè)線程獲得鎖,其他線程?定等待。
4.1 NSConditionLock相關(guān)的API
-
[xxxx lock];表示xxx期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內(nèi)部的condition) 那它能執(zhí)?此?以下代碼,如果已經(jīng)有其他線程獲得鎖(可能是條件鎖,或者?條件鎖),則等待,直?其他線程解鎖。 -
[xxx lockWhenCondition:A條件];表示如果沒有其他線程獲得該鎖,但是該鎖內(nèi)部的condition不等于A條件,它依然不能獲得鎖,仍然等待。如果內(nèi)部的condition等于A條件,并且沒有其他線程獲得該鎖,則進(jìn)?代碼區(qū),同時(shí)設(shè)置它獲得該鎖,其他任何線程都將等待它代碼的完成,直?它解鎖。 -
[xxx unlockWithCondition:A條件];表示釋放鎖,同時(shí)把內(nèi)部的condition設(shè)置為A條件。 -
return = [xxx lockWhenCondition:A條件 beforeDate:A時(shí)間];表示如果被鎖定(沒獲得鎖),并超過該時(shí)間則不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態(tài),這個(gè)函數(shù)的?的在于可以實(shí)現(xiàn)兩種狀態(tài)下的處理。
4.2 案例分析
案例如下:
- (void)lg_testConditonLock{
// 創(chuàng)建條件鎖 - 需要滿足條件2,否則不執(zhí)行
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
運(yùn)行結(jié)果見下圖:

分析結(jié)果:
-
NSConditionLock創(chuàng)建時(shí),設(shè)置的條件時(shí)2,也就是說需要滿足條件2,否則不執(zhí)行; -
線程 1調(diào)?[NSConditionLock lockWhenCondition:1],此時(shí)因?yàn)椴粷M?當(dāng)前條件,所以會(huì)進(jìn)?waiting狀態(tài),當(dāng)前進(jìn)?到waiting時(shí),會(huì)釋放當(dāng)前的互斥鎖。 - 此時(shí)當(dāng)前的
線程 3調(diào)?[NSConditionLock lock:],本質(zhì)上是調(diào)?[NSConditionLock lockBeforeDate:],這?不需要?對(duì)條件值,所以線程 3會(huì)打印。 - 接下來
線程 2執(zhí)?[NSConditionLock lockWhenCondition:2],因?yàn)闈M?條件值,所以線程 2會(huì)打印,打印完成后會(huì)調(diào)?[NSConditionLock unlockWithCondition:1],這個(gè)時(shí)候?qū)?code>value設(shè)置為1,并發(fā)送boradcast, 此時(shí)線程 1接收到當(dāng)前的信號(hào),喚醒執(zhí)?并打印。 - ?此當(dāng)前打印為
線程 3->線程 2->線程 1; -
[NSConditionLock lockWhenCondition:]:這?會(huì)根據(jù)傳?的condition值和Value值進(jìn)?對(duì)?,如果不相等,這?就會(huì)阻塞,進(jìn)?線程池,否則的話就繼續(xù)代碼執(zhí)?。 -
[NSConditionLock unlockWithCondition:]:這?會(huì)先更改當(dāng)前的value值,然后進(jìn)??播,喚醒當(dāng)前的線程。
5. 讀寫鎖的實(shí)現(xiàn)
5.1 讀寫鎖概念的分析理解
讀寫鎖實(shí)際是?種特殊的?旋鎖,它把對(duì)共享資源的訪問者劃分成讀者和寫者,讀者只對(duì)共享資源進(jìn)?讀訪問,寫者則需要對(duì)共享資源進(jìn)?寫操作。
這種鎖相對(duì)于?旋鎖??,能提?并發(fā)性,因?yàn)樵诙嗵幚砥飨到y(tǒng)中,它允許同時(shí)有多個(gè)讀者來訪問共享資源,最?可能的讀者數(shù)為實(shí)際的邏輯CPU數(shù)。寫者是排他性的,?個(gè)讀寫鎖同時(shí)只能有?個(gè)寫者或多個(gè)讀者(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者?有寫者。在讀寫鎖保持期間也是搶占失效的。
?次只有?個(gè)線程可以占有寫模式的讀寫鎖, 但是可以有多個(gè)線程同時(shí)占有讀模式的讀寫鎖. 正是因?yàn)檫@個(gè)特性,當(dāng)讀寫鎖是寫加鎖狀態(tài)時(shí), 在這個(gè)鎖被解鎖之前, 所有試圖對(duì)這個(gè)鎖加鎖的線程都會(huì)被阻塞.當(dāng)讀寫鎖在讀加鎖狀態(tài)時(shí), 所有試圖以讀模式對(duì)它進(jìn)?加鎖的線程都可以得到訪問權(quán), 但是如果線程希望以寫模式對(duì)此鎖進(jìn)?加鎖, 它必須直到所有的線程釋放鎖.
通常, 當(dāng)讀寫鎖處于讀模式鎖住狀態(tài)時(shí), 如果有另外線程試圖以寫模式加鎖, 讀寫鎖通常會(huì)阻塞隨后的讀模式鎖請(qǐng)求, 這樣可以避免讀模式鎖?期占?, ?等待的寫模式鎖請(qǐng)求?期阻塞.讀寫鎖適合于對(duì)數(shù)據(jù)結(jié)構(gòu)的讀次數(shù)?寫次數(shù)多得多的情況. 因?yàn)? 讀模式鎖定時(shí)可以共享, 以寫模式鎖住時(shí)意味著獨(dú)占, 所以讀寫鎖?叫共享-獨(dú)占鎖.
5.2 用到的API
-
pthread_rwlock_t lock; // 結(jié)構(gòu) -
pthread_rwlock_init(&lock, null); // 初始化 -
pthread_rwlock_rdlock(&lock); // 讀加鎖 -
pthread_rwlock_tryrdlock(&lock); // 讀嘗試加鎖 -
pthread_rwlock_wdlock(&lock); // 寫加鎖 -
pthread_rwlock_trywdlock(&lock); // 寫嘗試加鎖 -
pthread_rwlock_unlock(&lock); // 解鎖 -
pthread_rwlock_destory(&lock); // 銷毀
5.3 pthread_rwlock_t的使用
引入下面的案例,開啟十個(gè)線程,同時(shí)進(jìn)行讀寫操作, 要求:
- 可以實(shí)現(xiàn)多讀,多讀不互斥
- 單寫,讀寫互斥
- 寫寫互斥
實(shí)現(xiàn)代碼如下:
#import <Pthread.h>
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
@property (nonatomic,assign) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 0;
[self rwTest];
}
- (void)rwTest {
// 初始化
pthread_rwlock_init(&_lock, NULL);
// 全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 開啟讀寫
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self read];
});
// 寫
dispatch_async(queue, ^{
[self write];
});
}
}
// 讀流程
-(void)read{
// 讀加鎖
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"讀……%zd", self.ticketCount);
// 解鎖
pthread_rwlock_unlock(&_lock);
}
// 寫
-(void)write{
// 寫加鎖
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"寫……%zd", ++self.ticketCount);
// 解鎖
pthread_rwlock_unlock(&_lock);
}
@end
運(yùn)行結(jié)果如下:

通過上面的案例可以反映出
讀寫鎖同時(shí)只能有?個(gè)寫者,并且可以保證多讀同時(shí)進(jìn)行。
5.4 GCD柵欄函數(shù)的使用來實(shí)現(xiàn)讀寫鎖
實(shí)現(xiàn)代碼如下:
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) NSUInteger ticketCount;
// 并發(fā)隊(duì)列-多讀
@property (nonatomic, strong) dispatch_queue_t qCONCURRENT;
// 串行隊(duì)列-限制讀取順序
@property (nonatomic, strong) dispatch_queue_t qSERIAL;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 0;
// 隊(duì)列初始化
self.qCONCURRENT = dispatch_queue_create("selfCONCURRENT", DISPATCH_QUEUE_CONCURRENT);
self.qSERIAL = dispatch_queue_create("selfSERIAL", DISPATCH_QUEUE_SERIAL);
[self go_testReadWrite];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 寫 柵欄函數(shù),保證讀寫的互斥
dispatch_barrier_async(self.qCONCURRENT, ^{
[self writeAction];
});
}
#pragma read wirte
- (void)go_testReadWrite{
// 多線程讀
for (int i = 0; i < 2000; i++) {
dispatch_async(self.qCONCURRENT, ^{
[self readAction];
});
dispatch_async(self.qCONCURRENT, ^{
[self readAction];
});
dispatch_async(self.qCONCURRENT, ^{
[self readAction];
});
}
}
- (void)readAction {
// 保證讀取順序
dispatch_async(self.qSERIAL, ^{
sleep(1);
NSLog(@"讀 ..... %ld ------ %@", self.ticketCount, [NSThread currentThread]);
});
}
- (void)writeAction {
sleep(1);
NSLog(@"寫 ..... %ld ------ %@", ++self.ticketCount, [NSThread currentThread]);
}
運(yùn)行結(jié)果如下:

通過案例發(fā)現(xiàn)讀寫鎖的實(shí)現(xiàn)還是有多種的方式的。
總結(jié)
OC中鎖的探索就到此結(jié)束了,在學(xué)習(xí)過程中收獲滿滿。這對(duì)我們?cè)陂_發(fā)過程中保證線程的安全有很大的幫助哦。






