柚子快報邀請碼778899分享:cocoa iOS--鎖的學習
柚子快報邀請碼778899分享:cocoa iOS--鎖的學習
iOS--鎖的學習
鎖的介紹線程安全
鎖的分類自旋鎖和互斥鎖OSSpinLockos_unfair_lockpthread_mutexpthread_mutex的屬性
NSLockNSRecursiveLockNSConditionNSConditionLockdispatch_semaphoredispatch_queue@synchronizedatomicpthread_rwlock:讀寫鎖dispatch_barrier_async
鎖的性能比較
鎖的介紹
主要參考了iOS多線程安全-13種線程鎖? 在iOS開發(fā)中,鎖是一種用于管理并發(fā)訪問共享資源的機制。多個線程可以同時訪問共享資源,但在某一時刻只允許一個線程對資源進行讀取或修改,以避免數(shù)據(jù)競爭和不一致性。 鎖的主要目的是確保在給定時間內(nèi)只有一個線程可以進入被鎖定的代碼塊或臨界區(qū)。當一個線程獲得了鎖并進入臨界區(qū)時,其他嘗試獲取鎖的線程會被阻塞,直到該線程釋放鎖。這樣可以保證在臨界區(qū)內(nèi)的代碼只會被一個線程執(zhí)行,從而避免競態(tài)條件和數(shù)據(jù)不一致性的問題。
在iOS中,有多種鎖機制可供選擇,每種鎖機制都有其特定的適用場景和性能特征。一些常見的鎖機制包括: 互斥鎖(Mutex):例如NSLock、pthread_mutex等?;コ怄i提供了基本的線程同步機制,通過對臨界區(qū)加鎖和解鎖來確保同一時間只有一個線程可以訪問。 遞歸鎖(Recursive Lock):例如NSRecursiveLock、pthread_mutex_recursive等。遞歸鎖允許同一線程多次獲取鎖,避免了死鎖情況。 條件鎖(Condition Lock):例如NSConditionLock、pthread_cond等。條件鎖在某些特定條件下才允許線程繼續(xù)執(zhí)行,否則線程會被阻塞。 信號量(Semaphore):例如dispatch_semaphore。信號量是一種更為通用的同步機制,可以用于控制多個線程的并發(fā)數(shù)量。 自旋鎖(Spin Lock):例如os_unfair_lock、OSSpinLock(已被棄用)等。自旋鎖在等待鎖時不會將線程阻塞,而是通過循環(huán)忙等的方式嘗試獲取鎖,適用于短時間內(nèi)鎖的競爭概率較低的情況。
線程安全
線程安全是指在多線程環(huán)境下,程序能夠正確地處理共享數(shù)據(jù)并保證數(shù)據(jù)的一致性和正確性。 在多線程編程中,多個線程可以同時訪問和修改共享的數(shù)據(jù)。如果沒有適當?shù)耐綑C制和線程安全的設計,可能會出現(xiàn)以下問題:
競態(tài)條件(Race Condition):多個線程同時對同一數(shù)據(jù)進行讀寫操作,導致結果依賴于線程執(zhí)行的順序,從而得到不確定的結果。數(shù)據(jù)競爭(Data Race):多個線程同時訪問和修改共享數(shù)據(jù),其中至少一個是寫操作,導致未定義的行為。
這里用一個比較經(jīng)典的例子,具體參考iOS多線程安全-13種線程鎖?
//賣票演示
- (void)ticketTest{
self.ticketsCount = 50;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}
}
//賣票
- (void)sellingTickets{
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"當前剩余票數(shù)-> %d", oldMoney);
}
運行結果如圖:
這里是一個典型的數(shù)據(jù)競爭問題,當多個線程同時對一個共享數(shù)據(jù)進行讀寫操作是很有可能會發(fā)生數(shù)據(jù)競爭,為了避免這種情況,我們往往通過線程同步來保證線程安全。實現(xiàn)線程同步的方法很多,鎖就是一種比較常見的用法 ;
鎖的分類
鎖是實現(xiàn)線程同步和保證數(shù)據(jù)訪問的一種機制,在多線程編程中起著重要的作用。根據(jù)實現(xiàn)方式和特性,鎖可以分為幾個不同的分類:
互斥鎖(Mutex Lock):互斥鎖是最常見和基本的鎖類型,用于實現(xiàn)互斥訪問共享資源。它提供了兩個狀態(tài):鎖定(被一個線程持有)和解鎖(無線程持有)。只有一個線程可以持有互斥鎖,其他線程在獲取鎖之前會被阻塞?;コ怄i可以防止多個線程同時訪問臨界區(qū),從而保證數(shù)據(jù)的一致性。讀寫鎖(Read-Write Lock):讀寫鎖也稱為共享-獨占鎖,它允許多個線程同時讀取共享資源,但只允許一個線程進行寫操作。讀操作之間不會互斥,而讀操作與寫操作之間是互斥的。讀寫鎖適用于讀多寫少的場景,可以提高并發(fā)性能。自旋鎖(Spin Lock):自旋鎖是一種忙等待鎖,它使用循環(huán)來反復檢查鎖是否被釋放,而不是將線程阻塞。當一個線程發(fā)現(xiàn)自旋鎖被其他線程持有時,它會一直循環(huán)等待,直到自旋鎖可用。自旋鎖適用于臨界區(qū)很小且短時間內(nèi)會釋放的情況。條件變量(Condition Variable):條件變量用于線程之間的等待和通知機制。它與互斥鎖結合使用,允許一個線程等待某個條件成立并在條件滿足時通知其他線程。等待條件的線程會釋放互斥鎖,以便其他線程可以訪問臨界區(qū)。條件變量常用于生產(chǎn)者-消費者問題等場景。屏障(Barrier):屏障用于多個線程在某個點上等待,直到所有線程都到達該點后才繼續(xù)執(zhí)行。它可以用于同步多個線程的操作,確保線程在達到某個階段之前都等待其他線程。
自旋鎖和互斥鎖
自旋鎖(Spin Lock): 自旋鎖是一種忙等待鎖,它使用循環(huán)來反復檢查鎖的狀態(tài),直到獲取到鎖為止。自旋鎖的特點如下:
自旋鎖適用于臨界區(qū)很小且短時間內(nèi)會釋放的情況,因為它需要持續(xù)占用CPU資源來進行自旋等待。自旋鎖不會導致線程的阻塞和切換,所以對于短時間內(nèi)能夠獲取到鎖的情況,自旋鎖的性能較好。自旋鎖不會改變線程的調(diào)度順序,因此在高優(yōu)先級線程和低優(yōu)先級線程之間,可能會導致優(yōu)先級反轉(zhuǎn)的問題。
互斥鎖是一種常見的線程同步機制,用于實現(xiàn)互斥訪問共享資源。在iOS中,常用的互斥鎖有NSLock和NSRecursiveLock?;コ怄i的特點如下:
互斥鎖允許一個線程持有鎖,其他線程在獲取鎖之前會被阻塞,從而實現(xiàn)對臨界區(qū)的互斥訪問?;コ怄i會導致線程的阻塞和切換,所以對于長時間占用鎖或等待時間不確定的情況,互斥鎖的性能可能較差?;コ怄i可以解決優(yōu)先級反轉(zhuǎn)的問題,因為它可以改變線程的調(diào)度順序,讓優(yōu)先級高的線程優(yōu)先獲取鎖。
互斥鎖:線程會從sleep(加鎖)——>running(解鎖),過程中有上下文的切換,cpu的搶占,信號的發(fā)送等開銷。 自旋鎖:線程一直是running(加鎖——>解鎖),死循環(huán)檢測鎖的標志位,機制不復雜。 對比 互斥鎖的起始原始開銷要高于自旋鎖,但是基本是一勞永逸,臨界區(qū)持鎖時間的大小并不會對互斥鎖的開銷造成影響,而自旋鎖是死循環(huán)檢測,加鎖全程消耗cpu,起始開銷雖然低于互斥鎖,但是隨著持鎖時間,加鎖的開銷是線性增長。
OSSpinLock
OSSpinLock是iOS舊版本中提供的一種自旋鎖(Spin Lock)實現(xiàn)。它通過忙等待的方式來獲取鎖,并且不會導致線程的阻塞和切換。不過需要注意的是,自旋鎖在iOS 10之后已被標記為廢棄,因為它存在優(yōu)先級反轉(zhuǎn)和性能問題。推薦使用更現(xiàn)代的互斥鎖實現(xiàn),如os_unfair_lock和pthread_mutex等。 使用時需要導入頭文件#import
os_unfair_lock
os_unfair_lock是iOS 10及以上版本引入的一種互斥鎖(Mutex Lock)實現(xiàn),用于實現(xiàn)線程同步和保護共享資源的訪問。相比于舊版本中的自旋鎖(OSSpinLock),os_unfair_lock采用了更高級的算法來解決優(yōu)先級反轉(zhuǎn)和性能問題。 os_unfair_lock的特點如下:
os_unfair_lock是一種互斥鎖,只能由一個線程持有,其他線程在獲取鎖之前會被阻塞。它可以解決優(yōu)先級反轉(zhuǎn)問題,確保高優(yōu)先級線程在等待鎖時能夠優(yōu)先獲取。os_unfair_lock的實現(xiàn)采用了更高級的算法,避免了自旋等待,減少了不必要的CPU開銷。os_unfair_lock的性能通常比舊版自旋鎖(OSSpinLock)更好,尤其在高并發(fā)情況下。 需要導入頭文件#import
@implementation ViewController {
os_unfair_lock _lock ;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self ticketTest] ;
}
//賣票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
_lock = OS_UNFAIR_LOCK_INIT ;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}
}
//賣票
- (void)sellingTickets{
os_unfair_lock_lock(&_lock) ;
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"當前剩余票數(shù)-> %d", oldMoney);
os_unfair_lock_unlock(&_lock) ;
}
@end
要注意一下,通常不建議將 os_unfair_lock 直接作為屬性來使用。os_unfair_lock 是一個結構體,不具備 Objective-C 對象的特性,因此不適合直接作為屬性。
pthread_mutex
pthread_mutex 是 POSIX 線程庫中提供的一種互斥鎖(mutex)類型,用于實現(xiàn)線程同步和互斥訪問共享資源。 主要的 pthread_mutex 函數(shù)如下:
pthread_mutex_init:用于初始化互斥鎖,并指定互斥鎖的屬性??梢允褂?NULL 作為第二個參數(shù)來使用默認屬性。pthread_mutex_destroy:用于銷毀互斥鎖。pthread_mutex_lock:加鎖操作,將互斥鎖加鎖,如果互斥鎖已經(jīng)被其他線程鎖定,則當前線程被阻塞直到互斥鎖可用。pthread_mutex_trylock:嘗試加鎖操作,如果互斥鎖已經(jīng)被其他線程鎖定,則該操作會立即返回,而不會阻塞當前線程。pthread_mutex_unlock:解鎖操作,釋放互斥鎖,允許其他線程獲得鎖。
@implementation ViewController {
// os_unfair_lock _lock ;
pthread_mutex_t _lock ;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self ticketTest] ;
}
//賣票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化鎖屬性
pthread_mutexattr_t attr ;
pthread_mutexattr_init(&attr) ;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
pthread_mutex_init(&_lock, &attr) ;//第二個參數(shù)可以為NULL,表示使用默認屬性PTHREAD_MUTEX_NORMAL
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}
}
//賣票
- (void)sellingTickets{
// os_unfair_lock_lock(&_lock) ;
pthread_mutex_lock(&_lock) ;
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"當前剩余票數(shù)-> %d", oldMoney);
// os_unfair_lock_unlock(&_lock) ;
pthread_mutex_unlock(&_lock) ;
}
@end
pthread_mutex的屬性
pthread_mutexattr_settype:設置互斥鎖的類型。常見選項有:
PTHREAD_MUTEX_NORMAL:普通互斥鎖,不支持遞歸。PTHREAD_MUTEX_RECURSIVE:遞歸互斥鎖,允許同一線程多次獲得鎖。PTHREAD_MUTEX_ERRORCHECK:錯誤檢查互斥鎖,會檢測多次鎖定同一互斥鎖的錯誤。PTHREAD_MUTEX_DEFAULT:根據(jù)實現(xiàn)的默認類型設置互斥鎖。
NSLock
**NSLock是對mutex普通鎖的封裝。**pthread_mutex_init(mutex, NULL); NSLock 遵循 NSLocking 協(xié)議。Lock 方法是加鎖,unlock 是解鎖,tryLock 是嘗試加鎖,如果失敗的話返回 NO,lockBeforeDate: 是在指定Date之前嘗試加鎖,如果在指定時間之前都不能加鎖,則返回NO
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
@end
@implementation ViewController {
// os_unfair_lock _lock ;
// pthread_mutex_t _lock ;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self ticketTest] ;
}
//賣票演示
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化鎖屬性
// pthread_mutexattr_t attr ;
// pthread_mutexattr_init(&attr) ;
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
// pthread_mutex_init(&_lock, &attr) ;//第二個參數(shù)可以為NULL,表示使用默認屬性PTHREAD_MUTEX_NORMAL
self.lock = [[NSLock alloc] init] ;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}
}
//賣票
- (void)sellingTickets{
// os_unfair_lock_lock(&_lock) ;
// pthread_mutex_lock(&_lock) ;
[self.lock lock] ;
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"當前剩余票數(shù)-> %d", oldMoney);
// os_unfair_lock_unlock(&_lock) ;
// pthread_mutex_unlock(&_lock) ;
[self.lock unlock] ;
}
//- (instancetype)init {
// self = [super init];
// if (self) {
// _lock = [[NSLock alloc] init];
// }
// return self;
//}
//- (void)lock {
// [_lock lock];
//}
//
//- (void)unlock {
// [_lock unlock];
//}
@end
NSRecursiveLock
NSRecursiveLock是對mutex遞歸鎖的封裝,API跟NSLock基本一致
- (void)ticketTest{
self.ticketsCount = 50;
//os_unfair_lock lock = OS_UNFAIR_LOCK_INIT ;
//初始化鎖屬性
// pthread_mutexattr_t attr ;
// pthread_mutexattr_init(&attr) ;
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ;
// pthread_mutex_init(&_lock, &attr) ;//第二個參數(shù)可以為NULL,表示使用默認屬性PTHREAD_MUTEX_NORMAL
self.lock = [[NSRecursiveLock alloc] init] ;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self sellingTickets];
}
});
}
}
//賣票
- (void)sellingTickets{
// os_unfair_lock_lock(&_lock) ;
// pthread_mutex_lock(&_lock) ;
[self.lock lock] ;
int oldMoney = self.ticketsCount;
sleep(.2);
oldMoney -= 1;
self.ticketsCount = oldMoney;
NSLog(@"當前剩余票數(shù)-> %d", oldMoney);
// os_unfair_lock_unlock(&_lock) ;
// pthread_mutex_unlock(&_lock) ;
[self.lock unlock] ;
}
NSCondition
NSCondition是對mutex和cond的封裝,更加面向?qū)ο?;
@interface NSCondition : NSObject
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name;
@end
NSCondition 類繼承自 NSObject 并遵循 NSLocking 協(xié)議。wait 方法用于使當前線程等待,直到接收到信號。 waitUntilDate: 方法使當前線程等待,直到接收到信號或者指定的日期超時。signal 方法用于喚醒一個等待中的線程。broadcast 方法用于喚醒所有等待中的線程。name 屬性是一個可選的字符串,用于標識 NSCondition 對象的名稱。
// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
[self.condition lock];
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"刪除了元素");
[self.condition unlock];
}
// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信號
[self.condition signal];
[self.condition unlock];
}
我的想法是這類對象有兩種線程等待的方式,一種是wait–signal方式,一種是lock–unlock方式 ;
NSConditionLock
NSConditionLock是對NSCondition的進一步封裝,可以設置具體的條件值
@interface NSConditionLock : NSObject
- (instancetype)initWithCondition:(NSInteger)condition;
@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;
@end
dispatch_semaphore
ispatch_semaphore 是 Grand Central Dispatch (GCD) 中的一個信號量機制,用于控制并發(fā)訪問共享資源的線程數(shù)量。這個之前GCD中講過了 ;
dispatch_queue
使用GCD的串行隊列也可以實現(xiàn)線程同步的
@synchronized
@synchronized 是 Objective-C 中用于實現(xiàn)線程安全的關鍵字之一。它提供了一種簡單的方式來保護共享資源,以防止多個線程同時訪問和修改該資源。 使用 @synchronized 的基本語法如下:
@synchronized (object) {
// 同步的代碼塊
// ...
}
@synchronized 關鍵字的工作原理是,在進入 @synchronized 代碼塊之前,它會獲取 object 的互斥鎖,以確保同一時間只有一個線程可以進入該代碼塊。當線程離開 @synchronized 代碼塊時,它會釋放鎖,允許其他線程繼續(xù)進入。
關于@synchronized 的底層原理可以去深入看看,但平時使用只要知道它會在代碼塊開頭加鎖,代碼塊末尾解鎖就行了
atomic
在Objective-C中,atomic是一種屬性修飾符,用于指定屬性的原子性。當一個屬性被聲明為atomic時,它意味著在對該屬性進行讀取和寫入操作時,將會保證操作的原子性。 原子性是指一個操作要么完全執(zhí)行,要么完全不執(zhí)行,不存在執(zhí)行過程中被中斷的情況。在多線程環(huán)境下,使用atomic修飾符可以確保對屬性的讀取和寫入操作是線程安全的。 當屬性被聲明為atomic時,編譯器會自動生成相關的同步代碼,以確保對屬性的訪問是原子操作。這樣可以防止多個線程同時對同一個屬性進行讀寫操作,避免出現(xiàn)數(shù)據(jù)競爭和不一致的情況。 需要注意的是,雖然atomic提供了一定程度的線程安全性,但它并不能完全保證線程安全。在高并發(fā)的多線程環(huán)境中,仍然需要額外的同步機制來確保數(shù)據(jù)的一致性和正確性。 相比之下,另一個屬性修飾符nonatomic則表示屬性是非原子的,它不會提供自動的線程安全保護。在多線程環(huán)境下,使用nonatomic修飾符可以提高性能,但需要開發(fā)人員自行處理線程安全問題。 總之,atomic屬性修飾符用于指定屬性的原子性,提供一定程度的線程安全性。在多線程環(huán)境中,可以使用atomic來確保對屬性的讀取和寫入操作是原子的,但仍需要注意其他的線程安全問題。
pthread_rwlock:讀寫鎖
//初始化鎖
pthread_rwlock_t lock;
pthread_rwlock_init(&_lock, NULL);
//讀加鎖
pthread_rwlock_rdlock(&_lock);
//讀嘗試加鎖
pthread_rwlock_trywrlock(&_lock)
//寫加鎖
pthread_rwlock_wrlock(&_lock);
//寫嘗試加鎖
pthread_rwlock_trywrlock(&_lock)
//解鎖
pthread_rwlock_unlock(&_lock);
//銷毀
pthread_rwlock_destroy(&_lock);
pthread_rwlock 是 POSIX 線程庫中的讀寫鎖(read-write lock)機制。它提供了一種多讀單寫的并發(fā)訪問控制機制,允許多個線程同時讀取共享資源,但只允許一個線程進行寫操作。 使用 pthread_rwlock 可以實現(xiàn)以下功能:
多讀單寫控制:多個線程可以同時獲取讀鎖(讀取共享資源),但只有一個線程可以獲取寫鎖(修改共享資源)。讀寫互斥:在寫鎖被持有期間,其他線程無法獲取讀鎖,以確保數(shù)據(jù)一致性。
dispatch_barrier_async
GCD中也有 ;
鎖的性能比較
性能從高到低排序
1、os_unfair_lock 2、OSSpinLock 3、dispatch_semaphore 4、pthread_mutex 5、dispatch_queue(DISPATCH_QUEUE_SERIAL) 6、NSLock 7、NSCondition 8、pthread_mutex(recursive) 9、NSRecursiveLock 10、NSConditionLock 11、@synchronized
柚子快報邀請碼778899分享:cocoa iOS--鎖的學習
相關閱讀
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權,聯(lián)系刪除。