- 多線程為我們帶來了很大便利,也提高了程序的執行效率,但同時也帶來了
Data race
(當至少有兩個線程同時訪問同一個變量,而且至少其中有一個是寫操作時,就發生了Data race
)。這時就要利用一些同步機制來確保數據的準確性,鎖
就是同步機制中的一種。
一、各種鎖
-
@synchronized
關鍵字加鎖 -
NSLock
對象鎖 -
NSCondition
條件鎖1 -
NSConditionLock
條件鎖2 -
NSRecursiveLock
遞歸鎖 -
pthread_mutex
互斥鎖(C語言) -
pthread_mutex(recursive)
互斥鎖2(遞歸鎖一種類似NSConditionLock) -
dispath_semaphore
信號量實現加鎖(GCD) -
OSSpinlock
自旋鎖(iOS10以后被廢棄,因其不安全,有可能造成死鎖) -
os_unfair_lock
自旋鎖(iOS之后才可以使用,代替OSSpinlock
的方案)
二、性能比對圖
lock_benchmark.png
性能測試Demo的GitHub地址:https://github.com/Yjunjie/MultithreadingAndLock/tree/master
參考鏈接:http://www.lxweimin.com/p/c9c5bc68449d
總體來說:
OSSpinLock
和dispatch_semaphore
的效率遠遠高于其他。
@synchronized
和NSConditionLock
效率較差。臨界區
:指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。
三、詳細介紹
- 自旋鎖:
如果共享數據被其他線程加鎖,那么當前線程會以死循環的方式等待解鎖,一旦訪問的資源被解鎖,則等待線程就會立即執行。自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的。
1、OSSpinLock(不安全,已廢棄)
可能造成死鎖的原因:
有可能在優先級比較低的線程里對共享資源進行加鎖了,然后高優先級的線程搶占了低優先級的調用CPU時間,導致高優先級的線程一直在等待低優先級的線程釋放鎖,然而低優先級根本沒法搶占高優先級的CPU時間。
這種情況我們稱作 優先級倒轉。
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
...
OSSpinLockUnlock(&lock);
2、os_unfair_lock
os_unfair_lock 是蘋果官方推薦的替換OSSpinLock的方案,但是它在iOS10.0以上的系統才可以調用。
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
- 互斥鎖(Mutex):
如果共享資源已經有其他線程加鎖了,線程會進入休眠狀態等待鎖。一旦被訪問的資源被解鎖,則等待資源的線程會被喚醒。
互斥鎖不會同時被兩個不同的線程同時得到。也就是如果是當前線程加的鎖,別的線程是沒有辦法獲取這個鎖,也就沒有辦法對他進行解鎖。
1、NSLock
Foundation框架中以對象形式暴露給開發者的一種鎖,在AFNetworking的AFURLSessionManager.m中應用如下:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
...
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
...
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
...
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
2、pthread_mutex
實際項目中: 在YYKit的YYMemoryCach中可以看到
- (instancetype)init {
...
pthread_mutex_init(&_lock, NULL);
...
}
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
...
}
3、@synchronized:
實際項目中:AFNetworking中 isNetworkActivityOccurring屬性的getter方法
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
- 條件鎖:
當進程的某些資源要求不滿足時就進入休眠等待,也就是鎖住了。直到滿足條件后,條件鎖打開,進程繼續運行。
1、NSCondition
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
遵循NSLocking
協議,使用的時候同樣是lock
,unlock
加解鎖,wait
是傻等,waitUntilDate:
方法是等一會,都會阻塞線程,signal
是喚起一個在等待的線程,broadcast
是廣播全部喚起。
NSCondition *lock = [[NSCondition alloc] init];
//Son 線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (No Money) {
[lock wait];
}
NSLog(@"The money has been used up.");
[lock unlock];
});
//Father線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"Work hard to make money.");
[lock signal];
[lock unlock];
});
2、NSConditionLock
@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;
- 遞歸鎖
特點:同一個線程可以加鎖N次而不會死鎖。
1、NSRecursiveLock:
NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到:
_lock = [NSRecursiveLock new];
- (void)dealloc {
[_lock lock];
...
...
[_lock unlock];
}
2、pthread_mutex(recursive)
pthread_mutex
鎖也支持遞歸,只需要設置PTHREAD_MUTEX_RECURSIVE
即可
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&lock);
pthread_mutex_unlock(&lock);
- 信號量加鎖
多元信號量允許多個線程訪問同一個資源,多元信號量簡稱信號量(Semaphore),對于允許多個線程并發訪問的資源,這是一個很好的選擇。一個初始值為N的信號量允許N個線程并發訪問。其實嚴格的來說信號量不能算鎖。而且如果信號量設置為1,我們可以把它當作互斥鎖來用
1、dispatch_semaphore
dispatch_semaphore在YYKit中的YYThreadSafeArray.m有所應用,YY大神有這樣一句注釋:
@discussion Generally, access performance is lower than NSMutableArray,
but higher than using @synchronized, NSLock, or pthread_mutex_t.
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);
補充:讀寫鎖(又稱共享-互斥鎖)
允許多個線程同時對同一個數據進行讀操作,而只允許一個線程進行寫操作。這是因為讀操作不會改變數據的內容,是安全的;而寫操作會改變數據的內容,是不安全的。
1、pthread_rwlock_t
//加讀鎖
pthread_rwlock_rdlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);
//加寫鎖
pthread_rwlock_wrlock(&rwlock);
//解鎖
pthread_rwlock_unlock(&rwlock);