互斥鎖:保證在任什麼時候候,都只有一個線程訪問對象。當獲取鎖操做失敗時,線程會進入睡眠,等待鎖釋放時被喚醒;ios
自旋鎖:與互斥鎖有點相似,只是自旋鎖 不會引發調用者睡眠,若是自旋鎖已經被別的執行單元保持,調用者就一直循環嘗試,直到該自旋鎖的保持者已經釋放了鎖;由於不會引發調用者睡眠,因此效率高於互斥鎖;程序員
自旋鎖缺點:面試
特殊的互斥鎖,加了遞歸功能swift
@synchronized (self) {
要鎖的代碼
}
複製代碼
打斷點使用匯編查看內部實現,可看到被執行的代碼會被下文中的兩句代碼包裹安全
objc_sync_enter
要鎖的代碼
objc_sync_exit
複製代碼
在objc中源碼查看bash
######### objc_sync_enter
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//判斷對象是否存在
SyncData* data = id2data(obj, ACQUIRE);//從表中取出須要鎖的數據
assert(data);
data->mutex.lock();//對數據加鎖
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil(); //若是對象不存在,什麼事情都不作!
}
return result;
}
######### SyncData
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex; //遞歸鎖
} SyncData;
複製代碼
@synchronized結論:多線程
nil
防止死鎖表
的結構存要鎖的對象坑點:在大量線程異步同時操做同一個對象時,由於遞歸鎖會不停的alloc/release,在某一個對象會是nil;而此時 @synchronized (obj) 會判斷obj==nil,就不會再加鎖,致使線程訪問衝突;eg併發
#import "KTest.h"
@interface KTest()
@property (nonatomic,strong) NSMutableArray *testArray;
@end
@implementation KTest
- (void)crash {
//_testArray
//nil 不加鎖 - old release
//hash objc - nil
for (int i = 0; i < 20000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (_testArray) {//在某一刻_testArray= nil,致使加鎖失敗
_testArray = [NSMutableArray array];
}
});
}
}
//解決NSLock
- (void)NO_crash {
NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i < 20000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
_testArray = [NSMutableArray array];
[lock unlock];
});
}
}
@end
複製代碼
在上面的例子裏咱們用NSLock去解決在大量線程異步同時操做同一個對象的內存安全問題;那咱們細看下NSLock的源碼,NSLock屬於Foundation,須要在Foundation中查找,我這裏是Swift版本的Foundation,我對源碼作了一些簡化方便查看異步
open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize(count: 1)
mutex.deallocate()
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
open var name: String?
}
複製代碼
上面源碼可看出:async
NSRecursiveLock
先看官方文檔上的話
遞歸調用示例://NSLock
- (void)NSLock_crash {
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testBlock)(int);
testBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value-->%d",value);
testBlock(value-1);//遞歸調用,用遞歸鎖
}
[lock unlock];
};
testBlock(10);
});
}
//遞歸鎖NSRecursiveLock
- (void)NSRecursiveLock_NO_crash {
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testBlock)(int);
testBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value-->%d",value);
testBlock(value-1);//遞歸調用,用遞歸鎖
}
[lock unlock];
};
testBlock(10);
});
}
複製代碼
在上面的例子已經說明了NSRecursiveLock可以處理遞歸調用;可是仍是要看看源碼
open class NSRecursiveLock: NSObject, NSLocking {
internal var mutex = _RecursiveMutexPointer.allocate(capacity: 1)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
public override init() {
super.init()
var attrib = pthread_mutexattr_t()
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize(count: 1)
mutex.deallocate()
deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex)
pthread_cond_broadcast(timeoutCond)
pthread_mutex_unlock(timeoutMutex)
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
open func lock(before limit: Date) -> Bool {
if pthread_mutex_trylock(mutex) == 0 {
return true
}
return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
}
open var name: String?
}
複製代碼
上面源碼可看出:
相對來講NSCondition用的比較少,但也須要了解。先看源碼
open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
deinit {
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
}
open func lock() {
pthread_mutex_lock(mutex)
}
open func unlock() {
pthread_mutex_unlock(mutex)
}
open func wait() {
pthread_cond_wait(cond, mutex)
}
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
open func signal() {
pthread_cond_signal(cond)
}
open func broadcast() {
// 彙編分析 - 猜 (多看多玩)
pthread_cond_broadcast(cond) // wait signal
}
open var name: String?
}
複製代碼
由上面源碼能夠看出:
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeAllObjects];
NSLog(@"array removeAllObjects");
[lock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保證讓線程2的代碼後執行
[lock lock];
[array addObject:@1];
NSLog(@"array addObject:@1");
[lock signal];
[lock unlock];
});
複製代碼
先看源碼
open class NSConditionLock : NSObject, NSLocking {
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distantPast)
}
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open var name: String?
}
複製代碼
由源碼可知:
internal var _thread: _swift_CFThreadRef?
:_thread就是當前能夠同事操做的線程數,經過搭配NSCondition能夠達到dispatch_semaphore的效果lock(before: Date.distantFuture)
:也有超時時間 用法示例:#pragma mark -- NSConditionLock
- (void)testConditonLock{
// 信號量
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];
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
複製代碼
dispatch_semaphore 是 GCD 用來同步的一種方式,與他相關的只有三個函數,一個是建立信號量,一個是等待信號,一個是發送信號。
dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
複製代碼
dispatch_semaphore 和 NSConditionLock 相似,都是一種基於信號的同步方式,但 NSCondition 信號只能發送,不能保存(若是沒有線程在等待,則發送的信號會失效)。而 dispatch_semaphore 能保存發送的信號。dispatch_semaphore 的核心是 dispatch_semaphore_t 類型的信號量。 dispatch_semaphore_create(1) 方法能夠建立一個 dispatch_semaphore_t 類型的信號量,設定信號量的初始值爲 1。注意,這裏的傳入的參數必須大於或等於 0,不然 dispatch_semaphore_create 會返回 NULL。 dispatch_semaphore_wait(signal, overTime); 方法會判斷 signal 的信號值是否大於 0。大於 0 不會阻塞線程,消耗掉一個信號,執行後續任務。若是信號值爲 0,該線程會和 NSCondition 同樣直接進入 waiting 狀態,等待其餘線程發送信號喚醒線程去執行後續任務,或者當 overTime 時限到了,也會執行後續任務。 dispatch_semaphore_signal(signal); 發送信號,若是沒有等待的線程接受信號,則使 signal 信號值加一(作到對信號的保存)。
用法:
- (void)testDispatch_semaphore_t {
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime);
sleep(2);
NSLog(@"線程1");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(signal, overTime);
NSLog(@"線程2");
dispatch_semaphore_signal(signal);
});
}
複製代碼
從上面的實例代碼能夠看到,一個 dispatch_semaphore_wait(signal, overTime); 方法會去對應一個 dispatch_semaphore_signal(signal); 看起來像 NSLock 的 lock 和 unlock,其實能夠這樣理解,區別只在於有信號量這個參數,lock unlock 只能同一時間,一個線程訪問被保護的臨界區,而若是 dispatch_semaphore 的信號量初始值爲 x ,則能夠有 x 個線程同時訪問被保護的臨界區。
在iOS10 以前,OSSpinLock 是一種自旋鎖,也只有加鎖,解鎖,嘗試加鎖三個方法。和 NSLock 不一樣的是 NSLock 請求加鎖失敗的話,會先輪詢,但一秒事後便會使線程進入 waiting 狀態,等待喚醒。而 OSSpinLock 會一直輪詢,等待時會消耗大量 CPU 資源,不適用於較長時間的任務。而由於OSSpinLock再也不線程安全,在iOS10以後OSSpinLock被廢棄內部封裝了os_unfair_lock
,os_unfair_lock
也是一種互斥鎖不會忙等。 typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);
bool OSSpinLockTry( volatile OSSpinLock *__lock );
void OSSpinLockLock( volatile OSSpinLock *__lock );
void OSSpinLockUnlock( volatile OSSpinLock *__lock );
複製代碼
用例示範:
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"線程1");
sleep(10);
OSSpinLockUnlock(&theLock);
NSLog(@"線程1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
OSSpinLockLock(&theLock);
NSLog(@"線程2");
OSSpinLockUnlock(&theLock);
});
2016-08-19 20:25:13.526 ThreadLockControlDemo[2856:316247] 線程1
2016-08-19 20:25:23.528 ThreadLockControlDemo[2856:316247] 線程1解鎖成功
2016-08-19 20:25:23.529 ThreadLockControlDemo[2856:316260] 線程2
複製代碼
讀寫鎖是一種特殊的的自旋鎖;它能作到多讀單寫; 實現: 併發隊列 + dispatch_barrier_async
########### .h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RF_RWLock : NSObject
// 讀數據
- (id)rf_objectForKey:(NSString *)key;
// 寫數據
- (void)rf_setObject:(id)obj forKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
########### .m文件
#import "RF_RWLock.h"
@interface RF_RWLock ()
// 定義一個併發隊列:
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 用戶數據中心, 可能多個線程須要數據訪問:
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;
@end
@implementation RF_RWLock
- (id)init{
self = [super init];
if (self){
// 建立一個併發隊列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 建立數據字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark - 讀數據
- (id)rf_objectForKey:(NSString *)key{
__block id obj;
// 同步讀取指定數據:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 寫數據
- (void)rf_setObject:(id)obj forKey:(NSString *)key{
// 異步柵欄調用設置數據:
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
});
}
@end
複製代碼
1.atomic的原理?
atomic
在對象get/set
的時候,會有一個spinlock_t
控制。即當兩個線程A和B,若是A正在執行getter
時,B若是想要執行settet
,就要等A執行getter
完成後才能執行
2.atomic
修飾的屬性絕對安全嗎?
atomic
只保證set/get
方法安全,可是當多個線程不使用set/get
方法訪問時,就再也不安全;atomic
屬性和property
的多線程安全並無什麼直接的聯繫,多線程安全仍是要程序員本身保障atomic
的因爲使用了自旋鎖,性能比nonatomic
慢20倍參考 iOS 中的八大鎖