淺談iOS中的鎖的介紹及使用
在平時(shí)的開(kāi)發(fā)中經(jīng)常使用到多線(xiàn)程,在使用多線(xiàn)程的過(guò)程中,難免會(huì)遇到資源競(jìng)爭(zhēng)的問(wèn)題,那我們?cè)趺磥?lái)避免出現(xiàn)這種問(wèn)題那?
線(xiàn)程安全是什么?
當(dāng)一個(gè)線(xiàn)程訪(fǎng)問(wèn)數(shù)據(jù)的時(shí)候,其他的線(xiàn)程不能對(duì)其進(jìn)行訪(fǎng)問(wèn),直到該線(xiàn)程訪(fǎng)問(wèn)完畢。簡(jiǎn)單來(lái)講就是在同一時(shí)刻,對(duì)同一個(gè)數(shù)據(jù)操作的線(xiàn)程只有一個(gè)。只有確保了這樣,才能使數(shù)據(jù)不會(huì)被其他線(xiàn)程影響。而線(xiàn)程不安全,則是在同一時(shí)刻可以有多個(gè)線(xiàn)程對(duì)該數(shù)據(jù)進(jìn)行訪(fǎng)問(wèn),從而得不到預(yù)期的結(jié)果。
比如寫(xiě)文件和讀文件,當(dāng)一個(gè)線(xiàn)程在寫(xiě)文件的時(shí)候,理論上來(lái)說(shuō),如果這個(gè)時(shí)候另一個(gè)線(xiàn)程來(lái)直接讀取的話(huà),那么得到的結(jié)果可能是你無(wú)法預(yù)料的。
怎么來(lái)保證線(xiàn)程安全?
通常我們使用鎖的機(jī)制來(lái)保證線(xiàn)程安全,即確保同一時(shí)刻只有同一個(gè)線(xiàn)程來(lái)對(duì)同一個(gè)數(shù)據(jù)源進(jìn)行訪(fǎng)問(wèn)。
YY大神 的 不再安全的 OSSpinLock 這邊博客中列出了各種鎖以及性能比較:
性能對(duì)比
這里性能比較的只是加鎖立馬解鎖的時(shí)間消耗,并沒(méi)有計(jì)算競(jìng)爭(zhēng)時(shí)候的時(shí)間消耗。
鎖的介紹及簡(jiǎn)單使用
1.@synchronized
@synchronized是 iOS 中最常見(jiàn)的鎖,用法很簡(jiǎn)單:
- (void)viewDidLoad { [super viewDidLoad]; [self synchronized]; } - (void)synchronized { NSObject * cjobj = [NSObject new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(cjobj){ NSLog(@"線(xiàn)程1開(kāi)始"); sleep(3); NSLog(@"線(xiàn)程1結(jié)束"); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); @synchronized(cjobj){ NSLog(@"線(xiàn)程2"); } }); }
控制臺(tái)輸出:
2017-10-18 11:35:13.459194+0800 Thread-Lock[24855:431100] 線(xiàn)程1開(kāi)始
2017-10-18 11:35:16.460210+0800 Thread-Lock[24855:431100] 線(xiàn)程1結(jié)束
2017-10-18 11:35:16.460434+0800 Thread-Lock[24855:431101] 線(xiàn)程2
從上面的控制臺(tái)輸出時(shí)間可以看出來(lái),在線(xiàn)程 1 內(nèi)容全部輸出之后,才輸出了線(xiàn)程 2 的內(nèi)容,“線(xiàn)程1結(jié)束”與“線(xiàn)程2”都是在“線(xiàn)程1開(kāi)始”3 秒后輸出的。
@synchronized(cjobj) 指令使用的 cjobj 為該鎖的唯一標(biāo)識(shí),只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿(mǎn)足互斥,如果線(xiàn)程 2 中的 @synchronized(cjobj) 改為 @synchronized(self) ,那么線(xiàn)程 2 就不會(huì)被阻塞,@synchronized 指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對(duì)象,便可以實(shí)現(xiàn)鎖的機(jī)制,但作為一種預(yù)防措施,@synchronized 塊會(huì)隱式的添加一個(gè)異常處理例程來(lái)保護(hù)代碼,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。所以如果不想讓隱式的異常處理例程帶來(lái)額外的開(kāi)銷(xiāo),你可以考慮使用鎖對(duì)象。
@sychronized(cjobj){} 內(nèi)部 cjobj 被釋放或被設(shè)為 nil 不會(huì)影響鎖的功能,但如果 cjobj 一開(kāi)始就是 nil,那就會(huì)丟失了鎖的功能了。
2.NSLock
先看看iOS中NSLock類(lèi)的.h文件,從代碼中可以看出,該類(lèi)分成了幾個(gè)子類(lèi):NSLock、NSConditionLock、NSRecursiveLock、NSCondition,然后有一個(gè) NSLocking 協(xié)議:
@protocol NSLocking - (void)lock; - (void)unlock; @end
雖然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 協(xié)議,但是它們并不相同。
2.1 NSLock
NSLock 實(shí)現(xiàn)了最基本的互斥鎖,遵循了 NSLocking 協(xié)議,通過(guò) lock 和 unlock 來(lái)進(jìn)行鎖定和解鎖。
源碼內(nèi)容:
@interface NSLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); @end
用法:
- (void)viewDidLoad { [super viewDidLoad]; [self nslock]; } - (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"線(xiàn)程1加鎖成功"); sleep(2); [cjlock unlock]; NSLog(@"線(xiàn)程1解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lock]; NSLog(@"線(xiàn)程2加鎖成功"); [cjlock unlock]; NSLog(@"線(xiàn)程2解鎖成功"); }); }
控制臺(tái)輸出:
2017-10-19 15:03:58.868708+0800 Thread-Lock[39059:846493] 線(xiàn)程1加鎖成功
2017-10-19 15:04:00.872714+0800 Thread-Lock[39059:846493] 線(xiàn)程1解鎖成功
2017-10-19 15:04:00.872722+0800 Thread-Lock[39059:846492] 線(xiàn)程2加鎖成功
2017-10-19 15:04:00.873000+0800 Thread-Lock[39059:846492] 線(xiàn)程2解鎖成功
- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"線(xiàn)程1加鎖成功"); sleep(2); [cjlock unlock]; NSLog(@"線(xiàn)程1解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock tryLock]) { NSLog(@"線(xiàn)程3加鎖成功"); [cjlock unlock]; NSLog(@"線(xiàn)程3解鎖成功"); }else { NSLog(@"線(xiàn)程3加鎖失敗"); } }); }
控制臺(tái)輸出:
2017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849171] 線(xiàn)程1加鎖成功
2017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849169] 線(xiàn)程3加鎖失敗
2017-10-19 15:05:40.629969+0800 Thread-Lock[39118:849171] 線(xiàn)程1解鎖成功
- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"線(xiàn)程1加鎖成功"); sleep(2); [cjlock unlock]; NSLog(@"線(xiàn)程1解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(3); if ([cjlock tryLock]) { NSLog(@"線(xiàn)程4加鎖成功"); [cjlock unlock]; NSLog(@"線(xiàn)程4解鎖成功"); }else { NSLog(@"線(xiàn)程4加鎖失敗"); } }); }
控制臺(tái)輸出:
2017-10-19 15:07:14.872279+0800 Thread-Lock[39166:851060] 線(xiàn)程1加鎖成功
2017-10-19 15:07:16.876108+0800 Thread-Lock[39166:851060] 線(xiàn)程1解鎖成功
2017-10-19 15:07:17.876208+0800 Thread-Lock[39166:851052] 線(xiàn)程4加鎖成功
2017-10-19 15:07:17.876527+0800 Thread-Lock[39166:851052] 線(xiàn)程4解鎖成功
- (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"線(xiàn)程1加鎖成功"); sleep(2); [cjlock unlock]; NSLog(@"線(xiàn)程1解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"線(xiàn)程5加鎖成功"); [cjlock unlock]; NSLog(@"線(xiàn)程5解鎖成功"); }else { NSLog(@"線(xiàn)程5加鎖失敗"); } }); }
控制臺(tái)輸出:
2017-10-19 15:08:39.705131+0800 Thread-Lock[39204:852782] 線(xiàn)程1加鎖成功
2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852782] 線(xiàn)程1解鎖成功
2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852784] 線(xiàn)程5加鎖成功
2017-10-19 15:08:41.708935+0800 Thread-Lock[39204:852784] 線(xiàn)程5解鎖成功
注意:lock與unlock操作必須在同一線(xiàn)程,否則結(jié)果不確定甚至?xí)鹚梨i
由以上內(nèi)容總結(jié):
- 除 lock 和 unlock 方法外,NSLock 還提供了 tryLock 和 lockBeforeDate:兩個(gè)方法。
- 由上面的結(jié)果可以看到 tryLock 并不會(huì)阻塞線(xiàn)程,[cjlock tryLock] 能加鎖返回 YES,不能加鎖返回 NO,然后都會(huì)執(zhí)行后續(xù)代碼。
- 這里順便提一下 trylock 和 lock 使用場(chǎng)景:當(dāng)前線(xiàn)程鎖失敗,也可以繼續(xù)其它任務(wù),用 trylock 合適;當(dāng)前線(xiàn)程只有鎖成功后,才會(huì)做一些有意義的工作,那就 lock,沒(méi)必要輪詢(xún) trylock。以下的鎖都是這樣。
- lockBeforeDate: 方法會(huì)在所指定 Date 之前嘗試加鎖,會(huì)阻塞線(xiàn)程,如果在指定時(shí)間之前都不能加鎖,則返回 NO,指定時(shí)間之前能加鎖,則返回 YES。
- 由于是互斥鎖,當(dāng)一個(gè)線(xiàn)程進(jìn)行訪(fǎng)問(wèn)的時(shí)候,該線(xiàn)程獲得鎖,其他線(xiàn)程進(jìn)行訪(fǎng)問(wèn)的時(shí)候,將被操作系統(tǒng)掛起,直到該線(xiàn)程釋放鎖,其他線(xiàn)程才能對(duì)其進(jìn)行訪(fǎng)問(wèn),從而卻確保了線(xiàn)程安全。但是如果連續(xù)鎖定兩次,則會(huì)造成死鎖問(wèn)題。
2.2 NSRecursiveLock
NSRecursiveLock 是遞歸鎖,顧名思義,可以被一個(gè)線(xiàn)程多次獲得,而不會(huì)引起死鎖。它記錄了成功獲得鎖的次數(shù),每一次成功的獲得鎖,必須有一個(gè)配套的釋放鎖和其對(duì)應(yīng),這樣才不會(huì)引起死鎖。NSRecursiveLock 會(huì)記錄上鎖和解鎖的次數(shù),當(dāng)二者平衡的時(shí)候,才會(huì)釋放鎖,其它線(xiàn)程才可以上鎖成功。
源碼內(nèi)容:
@interface NSRecursiveLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); @end
用法:
- (void)viewDidLoad { [super viewDidLoad]; [self nsrecursivelock]; } - (void)nsrecursivelock{ NSRecursiveLock * cjlock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { [cjlock lock]; NSLog(@"%d加鎖成功",value); if (value > 0) { NSLog(@"value:%d", value); RecursiveBlock(value - 1); } [cjlock unlock]; NSLog(@"%d解鎖成功",value); }; RecursiveBlock(3); }); }
控制臺(tái)輸出:
2017-10-19 16:15:40.584213+0800 Thread-Lock[39579:894111] 3加鎖成功
2017-10-19 16:15:40.584387+0800 Thread-Lock[39579:894111] value:3
2017-10-19 16:15:40.584552+0800 Thread-Lock[39579:894111] 2加鎖成功
2017-10-19 16:15:40.584635+0800 Thread-Lock[39579:894111] value:2
2017-10-19 16:15:40.584810+0800 Thread-Lock[39579:894111] 1加鎖成功
2017-10-19 16:15:40.585267+0800 Thread-Lock[39579:894111] value:1
2017-10-19 16:15:40.585714+0800 Thread-Lock[39579:894111] 0加鎖成功
2017-10-19 16:15:40.585906+0800 Thread-Lock[39579:894111] 0解鎖成功
2017-10-19 16:15:40.586138+0800 Thread-Lock[39579:894111] 1解鎖成功
2017-10-19 16:15:40.586217+0800 Thread-Lock[39579:894111] 2解鎖成功
2017-10-19 16:15:40.586314+0800 Thread-Lock[39579:894111] 3解鎖成功
由以上內(nèi)容總結(jié):
如果用 NSLock 的話(huà),cjlock 先鎖上了,但未執(zhí)行解鎖的時(shí)候,就會(huì)進(jìn)入遞歸的下一層,而再次請(qǐng)求上鎖,阻塞了該線(xiàn)程,線(xiàn)程被阻塞了,自然后面的解鎖代碼不會(huì)執(zhí)行,而形成了死鎖。而 NSRecursiveLock 遞歸鎖就是為了解決這個(gè)問(wèn)題。
2.3 NSConditionLock
NSConditionLock 對(duì)象所定義的互斥鎖可以在使得在某個(gè)條件下進(jìn)行鎖定和解鎖,它和 NSLock 類(lèi)似,都遵循 NSLocking 協(xié)議,方法都類(lèi)似,只是多了一個(gè) condition 屬性,以及每個(gè)操作都多了一個(gè)關(guān)于 condition 屬性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以稱(chēng)為條件鎖。
- 只有 condition 參數(shù)與初始化時(shí)候的 condition 相等,lock 才能正確進(jìn)行加鎖操作。
- unlockWithCondition: 并不是當(dāng) condition 符合條件時(shí)才解鎖,而是解鎖之后,修改 condition 的值。
源碼內(nèi)容:
@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; @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); @end
用法:
- (void)viewDidLoad { [super viewDidLoad]; [self nsconditionlock]; } - (void)nsconditionlock { NSConditionLock * cjlock = [[NSConditionLock alloc] initWithCondition:0]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@"線(xiàn)程1加鎖成功"); sleep(1); [cjlock unlock]; NSLog(@"線(xiàn)程1解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lockWhenCondition:1]; NSLog(@"線(xiàn)程2加鎖成功"); [cjlock unlock]; NSLog(@"線(xiàn)程2解鎖成功"); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(2); if ([cjlock tryLockWhenCondition:0]) { NSLog(@"線(xiàn)程3加鎖成功"); sleep(2); [cjlock unlockWithCondition:2]; NSLog(@"線(xiàn)程3解鎖成功"); } else { NSLog(@"線(xiàn)程3嘗試加鎖失敗"); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ([cjlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"線(xiàn)程4加鎖成功"); [cjlock unlockWithCondition:1]; NSLog(@"線(xiàn)程4解鎖成功"); } else { NSLog(@"線(xiàn)程4嘗試加鎖失敗"); } }); }
控制臺(tái)輸出:
2017-10-19 15:09:44.010992+0800 Thread-Lock[39230:853946] 線(xiàn)程1加鎖成功
2017-10-19 15:09:45.012045+0800 Thread-Lock[39230:853946] 線(xiàn)程1解鎖成功
2017-10-19 15:09:46.012692+0800 Thread-Lock[39230:853947] 線(xiàn)程3加鎖成功
2017-10-19 15:09:48.016536+0800 Thread-Lock[39230:853947] 線(xiàn)程3解鎖成功
2017-10-19 15:09:48.016564+0800 Thread-Lock[39230:853944] 線(xiàn)程4加鎖成功
2017-10-19 15:09:48.017039+0800 Thread-Lock[39230:853944] 線(xiàn)程4解鎖成功
2017-10-19 15:09:48.017040+0800 Thread-Lock[39230:853945] 線(xiàn)程2加鎖成功
2017-10-19 15:09:48.017215+0800 Thread-Lock[39230:853945] 線(xiàn)程2解鎖成功
由以上內(nèi)容總結(jié):
- 在線(xiàn)程 1 解鎖成功之后,線(xiàn)程 2 并沒(méi)有加鎖成功,而是繼續(xù)等了 1 秒之后線(xiàn)程 3 加鎖成功,這是因?yàn)榫€(xiàn)程 2 的加鎖條件不滿(mǎn)足,初始化時(shí)候的 condition 參數(shù)為 0,而線(xiàn)程 2
- 加鎖條件是 condition 為 1,所以線(xiàn)程 2 加鎖失敗。
- lockWhenCondition 與 lock 方法類(lèi)似,加鎖失敗會(huì)阻塞線(xiàn)程,所以線(xiàn)程 2 會(huì)被阻塞著。
- tryLockWhenCondition: 方法就算條件不滿(mǎn)足,也會(huì)返回 NO,不會(huì)阻塞當(dāng)前線(xiàn)程。
- lockWhenCondition:beforeDate:方法會(huì)在約定的時(shí)間內(nèi)一直等待 condition 變?yōu)?2,并阻塞當(dāng)前線(xiàn)程,直到超時(shí)后返回 NO。
- 鎖定和解鎖的調(diào)用可以隨意組合,也就是說(shuō) lock、lockWhenCondition:與unlock、unlockWithCondition: 是可以按照自己的需求隨意組合的。
2.4、NSCondition
NSCondition 是一種特殊類(lèi)型的鎖,通過(guò)它可以實(shí)現(xiàn)不同線(xiàn)程的調(diào)度。一個(gè)線(xiàn)程被某一個(gè)條件所阻塞,直到另一個(gè)線(xiàn)程滿(mǎn)足該條件從而發(fā)送信號(hào)給該線(xiàn)程使得該線(xiàn)程可以正確的執(zhí)行。比如說(shuō),你可以開(kāi)啟一個(gè)線(xiàn)程下載圖片,一個(gè)線(xiàn)程處理圖片。這樣的話(huà),需要處理圖片的線(xiàn)程由于沒(méi)有圖片會(huì)阻塞,當(dāng)下載線(xiàn)程下載完成之后,則滿(mǎn)足了需要處理圖片的線(xiàn)程的需求,這樣可以給定一個(gè)信號(hào),讓處理圖片的線(xiàn)程恢復(fù)運(yùn)行。
- NSCondition 的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線(xiàn)程檢查器,鎖上之后其它線(xiàn)程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線(xiàn)程,即線(xiàn)程是否要進(jìn)入 waiting 狀態(tài),如果進(jìn)入 waiting 狀態(tài),當(dāng)其它線(xiàn)程中的該鎖執(zhí)行 signal 或者 broadcast 方法時(shí),線(xiàn)程被喚醒,繼續(xù)運(yùn)行之后的方法。
- NSCondition 可以手動(dòng)控制線(xiàn)程的掛起與喚醒,可以利用這個(gè)特性設(shè)置依賴(lài)。
源碼內(nèi)容:
@interface NSCondition : NSObject <NSLocking> { @private void *_priv; } - (void)wait; //掛起線(xiàn)程 - (BOOL)waitUntilDate:(NSDate *)limit; //什么時(shí)候掛起線(xiàn)程 - (void)signal; // 喚醒一條掛起線(xiàn)程 - (void)broadcast; //喚醒所有掛起線(xiàn)程 @property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); @end
用法:
- (void)viewDidLoad { [super viewDidLoad]; [self nscondition]; } - (void)nscondition { NSCondition * cjcondition = [NSCondition new]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@"線(xiàn)程1線(xiàn)程加鎖"); [cjcondition wait]; NSLog(@"線(xiàn)程1線(xiàn)程喚醒"); [cjcondition unlock]; NSLog(@"線(xiàn)程1線(xiàn)程解鎖"); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@"線(xiàn)程2線(xiàn)程加鎖"); if ([cjcondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) { NSLog(@"線(xiàn)程2線(xiàn)程喚醒"); [cjcondition unlock]; NSLog(@"線(xiàn)程2線(xiàn)程解鎖"); } }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition signal]; }); }
控制臺(tái)輸出:
2017-10-19 17:15:48.410316+0800 Thread-Lock[40011:943638] 線(xiàn)程1線(xiàn)程加鎖
2017-10-19 17:15:48.410757+0800 Thread-Lock[40011:943640] 線(xiàn)程2線(xiàn)程加鎖
2017-10-19 17:15:50.414288+0800 Thread-Lock[40011:943638] 線(xiàn)程1線(xiàn)程喚醒
2017-10-19 17:15:50.414454+0800 Thread-Lock[40011:943638] 線(xiàn)程1線(xiàn)程解鎖
//如果 [cjcondition signal]; 改成 [cjcondition broadcast]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition broadcast]; });
控制臺(tái)輸出:
2017-10-19 17:18:08.054109+0800 Thread-Lock[40056:946099] 線(xiàn)程1線(xiàn)程加鎖
2017-10-19 17:18:08.054304+0800 Thread-Lock[40056:946096] 線(xiàn)程2線(xiàn)程加鎖
2017-10-19 17:18:10.056071+0800 Thread-Lock[40056:946099] 線(xiàn)程1線(xiàn)程喚醒
2017-10-19 17:18:10.056231+0800 Thread-Lock[40056:946099] 線(xiàn)程1線(xiàn)程解鎖
2017-10-19 17:18:10.056244+0800 Thread-Lock[40056:946096] 線(xiàn)程2線(xiàn)程喚醒
2017-10-19 17:18:10.056445+0800 Thread-Lock[40056:946096] 線(xiàn)程2線(xiàn)程解鎖
由以上內(nèi)容總結(jié):
- 在加上鎖之后,調(diào)用條件對(duì)象的 wait 或 waitUntilDate: 方法來(lái)阻塞線(xiàn)程,直到條件對(duì)象發(fā)出喚醒信號(hào)或者超時(shí)之后,再進(jìn)行之后的操作。
- signal 和 broadcast 方法的區(qū)別在于,signal 只是一個(gè)信號(hào)量,只能喚醒一個(gè)等待的線(xiàn)程,想喚醒多個(gè)就得多次調(diào)用,而 broadcast 可以喚醒所有在等待的線(xiàn)程。
3.dispatch_semaphore
dispatch_semaphore 使用信號(hào)量機(jī)制實(shí)現(xiàn)鎖,等待信號(hào)和發(fā)送信號(hào)。
- dispatch_semaphore 是 GCD 用來(lái)同步的一種方式,與他相關(guān)的只有三個(gè)函數(shù),一個(gè)是創(chuàng)建信號(hào)量,一個(gè)是等待信號(hào),一個(gè)是發(fā)送信號(hào)。
- dispatch_semaphore 的機(jī)制就是當(dāng)有多個(gè)線(xiàn)程進(jìn)行訪(fǎng)問(wèn)的時(shí)候,只要有一個(gè)獲得了信號(hào),其他線(xiàn)程的就必須等待該信號(hào)釋放。
常用相關(guān)API:
dispatch_semaphore_create(long value); dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout); dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);
用法:
- (void)viewDidLoad { [super viewDidLoad]; [self dispatch_semaphore]; } - (void)dispatch_semaphore { dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(semaphore, overTime); NSLog(@"線(xiàn)程1開(kāi)始"); sleep(5); NSLog(@"線(xiàn)程1結(jié)束"); dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(semaphore, overTime); NSLog(@"線(xiàn)程2開(kāi)始"); dispatch_semaphore_signal(semaphore); }); }
控制臺(tái)輸出:
2017-10-19 18:30:37.672490+0800 Thread-Lock[40569:993613] 線(xiàn)程1開(kāi)始
2017-10-19 18:30:42.673845+0800 Thread-Lock[40569:993613] 線(xiàn)程1結(jié)束
2017-10-19 18:30:42.674165+0800 Thread-Lock[40569:993612] 線(xiàn)程2開(kāi)始
//如果 overTime 改成 3 秒
控制臺(tái)輸出:
2017-10-19 18:32:32.078186+0800 Thread-Lock[40634:995921] 線(xiàn)程1開(kāi)始
2017-10-19 18:32:35.082943+0800 Thread-Lock[40634:995920] 線(xiàn)程2開(kāi)始
2017-10-19 18:32:37.083115+0800 Thread-Lock[40634:995921] 線(xiàn)程1結(jié)束
由以上內(nèi)容總結(jié):
- dispatch_semaphore 和 NSCondition 類(lèi)似,都是一種基于信號(hào)的同步方式,但 NSCondition 信號(hào)只能發(fā)送,不能保存(如果沒(méi)有線(xiàn)程在等待,則發(fā)送的信號(hào)會(huì)失效)。而 dispatch_semaphore 能保存發(fā)送的信號(hào)。dispatch_semaphore 的核心是 dispatch_semaphore_t 類(lèi)型的信號(hào)量。
- dispatch_semaphore_create(1) 方法可以創(chuàng)建一個(gè) dispatch_semaphore_t 類(lèi)型的信號(hào)量,設(shè)定信號(hào)量的初始值為 1。注意,這里的傳入的參數(shù)必須大于或等于 0,否則 dispatch_semaphore_create 會(huì)返回 NULL。
- dispatch_semaphore_wait(semaphore, overTime); 方法會(huì)判斷 semaphore 的信號(hào)值是否大于 0。大于 0 不會(huì)阻塞線(xiàn)程,消耗掉一個(gè)信號(hào),執(zhí)行后續(xù)任務(wù)。如果信號(hào)值為 0,該線(xiàn)程會(huì)和 NSCondition 一樣直接進(jìn)入 waiting 狀態(tài),等待其他線(xiàn)程發(fā)送信號(hào)喚醒線(xiàn)程去執(zhí)行后續(xù)任務(wù),或者當(dāng) overTime 時(shí)限到了,也會(huì)執(zhí)行后續(xù)任務(wù)。
- dispatch_semaphore_signal(semaphore); 發(fā)送信號(hào),如果沒(méi)有等待的線(xiàn)程接受信號(hào),則使 signal 信號(hào)值加一(做到對(duì)信號(hào)的保存)。
- 一個(gè) dispatch_semaphore_wait(semaphore, overTime); 方法會(huì)去對(duì)應(yīng)一個(gè) dispatch_semaphore_signal(semaphore); 看起來(lái)像 NSLock 的 lock 和 unlock,其實(shí)可以這樣理解,區(qū)別只在于有信號(hào)量這個(gè)參數(shù),lock unlock 只能同一時(shí)間,一個(gè)線(xiàn)程訪(fǎng)問(wèn)被保護(hù)的臨界區(qū),而如果 dispatch_semaphore 的信號(hào)量初始值為 x ,則可以有 x 個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)被保護(hù)的臨界區(qū)。
4.pthread_mutex 與 pthread_mutex(recursive)
pthread 表示 POSIX thread,定義了一組跨平臺(tái)的線(xiàn)程相關(guān)的 API,POSIX 互斥鎖是一種超級(jí)易用的互斥鎖,使用的時(shí)候:
- 只需要使用 pthread_mutex_init 初始化一個(gè) pthread_mutex_t,
- pthread_mutex_lock 或者 pthread_mutex_trylock 來(lái)鎖定 ,
- pthread_mutex_unlock 來(lái)解鎖,
- 當(dāng)使用完成后,記得調(diào)用 pthread_mutex_destroy 來(lái)銷(xiāo)毀鎖。
常用相關(guān)API:
pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable); pthread_mutex_lock(pthread_mutex_t * _Nonnull); pthread_mutex_trylock(pthread_mutex_t * _Nonnull); pthread_mutex_unlock(pthread_mutex_t * _Nonnull); pthread_mutex_destroy(pthread_mutex_t * _Nonnull);
用法:
//pthread_mutex - (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex]; } - (void)pthread_mutex { __block pthread_mutex_t cjlock; pthread_mutex_init(&cjlock, NULL); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&cjlock); NSLog(@"線(xiàn)程1開(kāi)始"); sleep(3); NSLog(@"線(xiàn)程1結(jié)束"); pthread_mutex_unlock(&cjlock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&cjlock); NSLog(@"線(xiàn)程2"); pthread_mutex_unlock(&cjlock); }); }
控制臺(tái)輸出:
2017-10-23 14:50:29.842180+0800 Thread-Lock[74478:1647362] 線(xiàn)程1開(kāi)始
2017-10-23 14:50:32.846786+0800 Thread-Lock[74478:1647362] 線(xiàn)程1結(jié)束
2017-10-23 14:50:32.847001+0800 Thread-Lock[74478:1647359] 線(xiàn)程2
//pthread_mutex(recursive) - (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex_recursive]; } - (void)pthread_mutex_recursive { __block pthread_mutex_t cjlock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&cjlock, &attr); pthread_mutexattr_destroy(&attr); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { pthread_mutex_lock(&cjlock); NSLog(@"%d加鎖成功",value); if (value > 0) { NSLog(@"value = %d", value); sleep(1); RecursiveBlock(value - 1); } NSLog(@"%d解鎖成功",value); pthread_mutex_unlock(&cjlock); }; RecursiveBlock(3); }); }
//控制臺(tái)輸出:
2017-10-23 15:31:51.599693+0800 Thread-Lock[74723:1668089] 3加鎖成功
2017-10-23 15:31:51.599912+0800 Thread-Lock[74723:1668089] value = 3
2017-10-23 15:31:52.602002+0800 Thread-Lock[74723:1668089] 2加鎖成功
2017-10-23 15:31:52.602317+0800 Thread-Lock[74723:1668089] value = 2
2017-10-23 15:31:53.604669+0800 Thread-Lock[74723:1668089] 1加鎖成功
2017-10-23 15:31:53.604957+0800 Thread-Lock[74723:1668089] value = 1
2017-10-23 15:31:54.607778+0800 Thread-Lock[74723:1668089] 0加鎖成功
2017-10-23 15:31:54.608109+0800 Thread-Lock[74723:1668089] 0解鎖成功
2017-10-23 15:31:54.608391+0800 Thread-Lock[74723:1668089] 1解鎖成功
2017-10-23 15:31:54.608622+0800 Thread-Lock[74723:1668089] 2解鎖成功
2017-10-23 15:31:54.608945+0800 Thread-Lock[74723:1668089] 3解鎖成功
由以上內(nèi)容總結(jié):
- 它的用法和 NSLock 的 lock unlock 用法一致,而它也有一個(gè) pthread_mutex_trylock 方法,pthread_mutex_trylock 和 tryLock 的區(qū)別在于,tryLock 返回的是 YES 和 NO,pthread_mutex_trylock 加鎖成功返回的是 0,失敗返回的是錯(cuò)誤提示碼。
- pthread_mutex(recursive) 作用和 NSRecursiveLock 遞歸鎖類(lèi)似。如果使用 pthread_mutex_init(&theLock, NULL); 初始化鎖的話(huà),上面的代碼的第二部分會(huì)出現(xiàn)死鎖現(xiàn)象,使用遞歸鎖就可以避免這種現(xiàn)象。
5. OSSpinLock
OSSpinLock 是一種自旋鎖,和互斥鎖類(lèi)似,都是為了保證線(xiàn)程安全的鎖。但二者的區(qū)別是不一樣的,對(duì)于互斥鎖,當(dāng)一個(gè)線(xiàn)程獲得這個(gè)鎖之后,其他想要獲得此鎖的線(xiàn)程將會(huì)被阻塞,直到該鎖被釋放。但自選鎖不一樣,當(dāng)一個(gè)線(xiàn)程獲得鎖之后,其他線(xiàn)程將會(huì)一直循環(huán)在哪里查看是否該鎖被釋放。所以,此鎖比較適用于鎖的持有者保存時(shí)間較短的情況下。
只有加鎖,解鎖,嘗試加鎖三個(gè)方法。
常用相關(guān)API:
typedef int32_t OSSpinLock; // 加鎖 void OSSpinLockLock( volatile OSSpinLock *__lock ); // 嘗試加鎖 bool OSSpinLockTry( volatile OSSpinLock *__lock ); // 解鎖 void OSSpinLockUnlock( volatile OSSpinLock *__lock );
用法:
#import <libkern/OSAtomic.h> - (void)viewDidLoad { [super viewDidLoad]; [self osspinlock]; } - (void)osspinlock { __block OSSpinLock theLock = OS_SPINLOCK_INIT; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); NSLog(@"線(xiàn)程1開(kāi)始"); sleep(3); NSLog(@"線(xiàn)程1結(jié)束"); OSSpinLockUnlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); sleep(1); NSLog(@"線(xiàn)程2"); OSSpinLockUnlock(&theLock); }); }
控制臺(tái)輸出:
2017-10-23 16:02:48.865501+0800 Thread-Lock[75025:1684487] 線(xiàn)程1開(kāi)始
2017-10-23 16:02:51.868736+0800 Thread-Lock[75025:1684487] 線(xiàn)程1結(jié)束
2017-10-23 16:02:52.922911+0800 Thread-Lock[75025:1684486] 線(xiàn)程2
YY大神 @ibireme 的文章也有說(shuō)這個(gè)自旋鎖存在優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題,具體文章可以戳 不再安全的 OSSpinLock,而 OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代。
6.os_unfair_lock
自旋鎖已經(jīng)不再安全,然后蘋(píng)果又整出來(lái)個(gè) os_unfair_lock,這個(gè)鎖解決了優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題。
常用相關(guān)API:
// 初始化 os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); // 加鎖 os_unfair_lock_lock(unfairLock); // 嘗試加鎖 BOOL b = os_unfair_lock_trylock(unfairLock); // 解鎖 os_unfair_lock_unlock(unfairLock); os_unfair_lock 用法和 OSSpinLock 基本一直,就不一一列出了。
總結(jié)
應(yīng)當(dāng)針對(duì)不同的操作使用不同的鎖,而不能一概而論哪種鎖的加鎖解鎖速度快。
- 其實(shí)每一種鎖基本上都是加鎖、等待、解鎖的步驟,理解了這三個(gè)步驟就可以幫你快速的學(xué)會(huì)各種鎖的用法。
- @synchronized 的效率最低,不過(guò)它的確用起來(lái)最方便,所以如果沒(méi)什么性能瓶頸的話(huà),可以選擇使用 @synchronized。
- 當(dāng)性能要求較高時(shí)候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保證線(xiàn)程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前兩個(gè)是比較好的選擇。既可以保證速度,又可以保證線(xiàn)程安全。
- 對(duì)于 NSLock 及其子類(lèi),速度來(lái)說(shuō) NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS點(diǎn)擊推送消息跳到應(yīng)用指定頁(yè)面方法
現(xiàn)在的推送用的越來(lái)越頻繁,幾乎每個(gè)應(yīng)用都開(kāi)始用到了。這篇文章主要介紹了iOS點(diǎn)擊推送消息跳到應(yīng)用指定頁(yè)面方法,有需要的可以了解一下。2016-11-11iOS 高效的分頁(yè)加載實(shí)現(xiàn)示例
本篇文章主要介紹了iOS 高效的分頁(yè)加載實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10iOS開(kāi)發(fā)之手勢(shì)gesture詳解
本篇文章介紹了iOS開(kāi)發(fā)之手勢(shì)gesture,現(xiàn)在分享給大家,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-11-11IOS開(kāi)發(fā)之為視圖繪制單(多)個(gè)圓角實(shí)例代碼
這篇文章主要介紹了IOS開(kāi)發(fā)之為視圖繪制單(多)個(gè)圓角實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02iOS 使用UITextField自定義搜索框 實(shí)現(xiàn)用戶(hù)輸入完之后“實(shí)時(shí)搜索”功能
這篇文章主要介紹了iOS 使用UITextField自定義搜索框 實(shí)現(xiàn)用戶(hù)輸入完之后“實(shí)時(shí)搜索”功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03IOS初始化控制器的實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了IOS初始化控制器的實(shí)現(xiàn)方法總結(jié)的相關(guān)資料,這里提供兩種實(shí)現(xiàn)方法分別是ViewControllViewController方法和 ViewControllViewController 與 xib方法,需要的朋友可以參考下2017-10-10IOS開(kāi)發(fā)中異步網(wǎng)絡(luò)請(qǐng)求上實(shí)現(xiàn)同步邏輯
這篇文章主要介紹了IOS開(kāi)發(fā)中異步網(wǎng)絡(luò)請(qǐng)求上實(shí)現(xiàn)同步邏輯的相關(guān)資料,需要的朋友可以參考下2017-03-03