lock是什么?我們在使用多線程的時候,可能會遇到多個線程訪問同一塊資源,這樣就很容易引發數據錯亂喝數據安全等問題,這時我們就需要保證每次只有一個線程訪問一塊資源,lock就出來啦。
1、OSSpinLock 自旋鎖
應用如下:
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 準備上鎖");
OSSpinLockLock(&oslock);
sleep(4);
NSLog(@"線程1");
OSSpinLockUnlock(&oslock);
NSLog(@"線程1 解鎖成功");
NSLog(@"--------------------------------------------------------");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 準備上鎖");
OSSpinLockLock(&oslock);
NSLog(@"線程2");
OSSpinLockUnlock(&oslock);
NSLog(@"線程2 解鎖成功");
});
從圖中可以看到,當我們鎖住線程1時,在同時鎖住線程2的情況下,線程2會一直等待(自旋鎖不會讓等待進入睡眠狀態),知道線程1的認為執行完畢,線程2會立即執行;
修改一下,注釋掉線程1中的解鎖代碼。
//OSSpinLockUnlock(&oslock);
我們看到注釋之后,程序繞過線程1,直接調用了線程2的解鎖方法,才會繼續執行線程1的任務,同一個鎖對象。正常情況下lock與unlock是成對出現的。
線程加鎖也會用失敗的可能,有時候也會考慮這種情況。這時候OSSpinLockTry()方法就比較合適,如果允許線程枷鎖失敗,也可以繼續其他任務,可以用tryLock。如果只有線程枷鎖成功才會做一些有意義的工作,那就lock。
2、dispatch_semaphore 信號量。
例子:
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //傳入值必須 >=0, 若傳入為0則阻塞線程并等待timeout,時間到后會執行其后的語句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1變成0
NSLog(@"線程1");
dispatch_semaphore_signal(signal); //signal 值 +1,變成1,執行線程1.
NSLog(@"線程1 發送信號");
NSLog(@"--------------------------------------------------------");
});
初始信號量大于0,所以沒有阻塞線程,直接執行了線程1,線程2.把初始信號量改為0.
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
3、pthread_mutex互斥鎖
在<<不再安全的的OSSpinlock>>中提到性能最好的OSSpinLock已經不再線程安全,換成了,pthread_mutex.
例子:
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 準備上鎖");
pthread_mutex_lock(&pLock);
sleep(3);
NSLog(@"線程1");
pthread_mutex_unlock(&pLock);
});
//1.線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 準備上鎖");
pthread_mutex_lock(&pLock);
NSLog(@"線程2");
pthread_mutex_unlock(&pLock);
});
pthread_mutex 中也有個pthread_mutex_trylock(&pLock),和上面提到的 OSSpinLockTry(&oslock)區別在于,前者可以加鎖時返回的是 0,否則返回一個錯誤提示碼;后者返回的 YES和NO
經過上面幾種例子,我們可以發現:加鎖后只能有一個線程訪問該對象,后面的線程需要排隊,并且 lock 和 unlock 是對應出現的,同一線程多次 lock 是不允許的,而遞歸鎖允許同一個線程在未釋放其擁有的鎖時反復對該鎖進行加鎖操作
static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且給它賦予默認
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //設置鎖類型,這邊是設置為遞歸鎖
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); //銷毀一個屬性對象,在重新進行初始化之前該結構不能重新使用
//1.線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
pthread_mutex_lock(&pLock);
if (value > 0) {
NSLog(@"value: %d", value);
RecursiveBlock(value - 1);
}
pthread_mutex_unlock(&pLock);
};
RecursiveBlock(5);
});
上面的代碼如果我們用 pthread_mutex_init(&pLock, NULL) 初始化會出現死鎖的情況,遞歸鎖能很好的避免這種情況的死鎖;
4、NSLock普通鎖
例子:
NSLock *lock = [NSLock new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程1 嘗試加速ing...");
[lock lock];
sleep(3);//睡眠5秒
NSLog(@"線程1");
[lock unlock];
NSLog(@"線程1解鎖成功");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"線程2 嘗試加速ing...");
BOOL x =? [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"線程2");
[lock unlock];
}else{
NSLog(@"失敗");
}
});
lock lockBeforeDate:
5、NSCondition,信號量。
@interface NSCondition:NSObject<NSLocking>{
@private
void *_priv
}
-(void)wait;//進入等待狀態
-(void)waitUntilDate:(NSDate *)limit;//讓一個線程等待一定時間
-(void)signal;//喚醒一個等待的線程
-(void)broadcast;//喚醒所有等待的線程
@property(nullable,copy)NSString *name;
例子:等待2秒
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"start");
[cLock lock];
[cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"線程1");
[cLock unlock];
});
喚醒等待的線程
NSCondition *cLock = [NSCondition new];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程1加鎖成功");
[cLock wait];
NSLog(@"線程1");
[cLock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"線程2加鎖成功");
[cLock wait];
NSLog(@"線程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"喚醒一個等待的線程");
[cLock signal];
});
[cLock broadcast];//喚醒所有的線程
6、@synchronized 條件鎖
@synchronized 用法最簡單的鎖。@synchronized所做的事情和lock類似,它防止不同的線程同時執行同一段代碼。但在某些情況下,相比使用NSLock,@synchronized使用更方便,可讀性更高。
下面實現一個線程安全的隊列:
@implementation ThreadSafeQueue
{
NSMutableArray *_elements;
NSLock *_lock;
}
- (instancetype)init
{
self = [super init];
if (self) {
_elements = [NSMutableArray array];
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)push:(id)element
{
[_lock lock];
[_elements addObject:element];
[_lock unlock];
}
@end
上面的 ThreadSafeQueue 類有個 init 方法,它初始化了一個 _elements 數組和一個 NSLock 實例。這個類還有個 push: 方法,它先獲取鎖、然后向數組中插入元素、最終釋放鎖。可能會有許多線程同時調用 push: 方法,但是 [_elements addObject:element] 這行代碼在任何時候將只會在一個線程上運行。
我們可以使用@synchronized結構更簡要的實現這些:
@implementation ThreadSafeQueue
{
NSMutableArray *_elements;
}
- (instancetype)init
{
self = [super init];
if (self) {
_elements = [NSMutableArray array];
}
return self;
}
- (void)push:(id)element
{
@synchronized (self) {
[_elements addObject:element];
}
}
在前面的例子中,”synchronized block” 與 [_lock lock] 和 [_lock unlock] 效果相同。你可以把它當成是鎖住 self,仿佛 self 就是個 NSLock。鎖在左括號 { 后面的任何代碼運行之前被獲取到,在右括號 }。
本文參考:http://blog.csdn.net/qq_30513483/article/details/52349968