ios中锁的种类
- NSLock
- synchronized 关键字
- pthread_mutex_t
- dispatch_semaphore_t (信号量)
- NSRecursiveLock递归锁
- NSConditionLock 条件锁
- NSCondition
- OSSpinLock 自旋锁
- pthread_mutex(recursive)]10. NSDistributedLock 分布式锁
NSLock
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLock * lck = [[NSLock alloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lck lock ]; //上锁
[self firstMethod];
sleep(30);
[lck unlock]; //解锁
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //确保线程1先执行
[lck lock];
[self secondMethod];
[lck unlock];
});
}
- (void)firstMethod
{
NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
NSLog(@"----%s",__func__);
}
@end
- 结果是secondMethod方法在线程1执行完毕30秒后才执行
- NSLock 是 Cocoa 提供给我们最基本的锁对象,这也是我们经常所使用的,除 lock 和 unlock 方法外,NSLock 还提供了 tryLock 和 lockBeforeDate: 两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),并不会阻塞线程,直接返回 NO。lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,如果在指定时间之前都不能加锁,则返回 NO
synchronized 关键字构建的锁
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
[self firstMethod];
sleep(30);
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //确保线程1先执行
@synchronized(self) {
[self secondMethod];
}
});
}
- (void)firstMethod
{
NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
NSLog(@"----%s",__func__);
}
- @synchronized(objc) 使用objc为唯一标识,这个标识可以是任何对象,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj) 改为@synchronized(other) 时,线程2就不会被阻塞,@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
使用C语言的 pthread_mutex_t 实现的锁
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
[self firstMethod];
sleep(30);
pthread_mutex_unlock(&mutex);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //确保线程1先执行
pthread_mutex_lock(&mutex);
[self secondMethod];
pthread_mutex_unlock(&mutex);
});
}
- (void)firstMethod
{
NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
NSLog(@"----%s",__func__);
}
@end
使用 GCD 来实现的”锁”,信号量
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self firstMethod];
sleep(5);
dispatch_semaphore_signal(semaphore);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //确保线程1先执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self secondMethod];
sleep(5);
dispatch_semaphore_signal(semaphore);
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2); //确保线程1,2先执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"hello world");
dispatch_semaphore_signal(semaphore);
});
}
- (void)firstMethod
{
NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
NSLog(@"----%s",__func__);
}
@end
output:
2016-07-18 23:40:10.562 test2[14274:1725000] -----[ViewController firstMethod]
2016-07-18 23:40:15.563 test2[14274:1725001] -----[ViewController secondMethod]
2016-07-18 23:40:20.568 test2[14274:1725002] hello world
-
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
; 如果semaphore计数大于等于1.计数-1,返回,程序继续运行。如果计数为0,则等待。这里设置的等待时间是一直等待。dispatch_semaphore_signal(semaphore)
;计数+1.在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。 - dispatch_semaphore_create的声明为:
dispatch_semaphore_t dispatch_semaphore_create(long value);
传入的参数为long
,输出一个dispatch_semaphore_t
类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
-
dispatch_semaphore_signal的声明为:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
这个函数会使传入的信号量dsema的值加1
-
dispatch_semaphore_wait的声明为:
ong dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个函数会使传入的信号量dsema的值减1;
这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,
且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
- dispatch_semaphore_signal的返回值
ispatch_semaphore_signal的返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理
的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一
个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。
当其返回不为0时,表示timeout发生
##NSRecursiveLock递归锁
- 平时我们在代码中使用锁的时候,最容易犯的一个错误就是造成死锁,而容易造成死锁的一种情形就是在递归或循环中
```objc
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSRecursiveLock *lck = [[NSRecursiveLock alloc]init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
static void(^blk)(NSInteger);
blk = ^(NSInteger i)
{
[lck lock];
if (i >0)
{
NSLog(@"递归,当前i的值为%zd",i);
sleep(2);
blk(i-1);
}
[lck unlock];
};
blk(3);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
sleep(1); //确保线程1先执行
[lck lock];
[self secondMethod];
[lck unlock];
});
}
- (void)firstMethod
{
NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
NSLog(@"----%s",__func__);
}
@end
output:
2016-07-19 00:17:04.633 test2[14346:1748726] 递归,当前i的值为3
2016-07-19 00:17:06.638 test2[14346:1748726] 递归,当前i的值为2
2016-07-19 00:17:08.642 test2[14346:1748726] 递归,当前i的值为1
2016-07-19 00:17:10.644 test2[14346:1748727] -----[ViewController secondMethod]
- 上的代码中,就是一种典型的死锁情况,因为在线程1中的递归 block 中,锁会被多次的 lock,所以自己也被阻塞了,由于以上的代码非常的简短,所以很容易能识别死锁,但在较为复杂的代码中,就不那么容易发现了,那么如何在递归或循环中正确的使用锁呢?此处的 lock 变量如果换用 NSRecursiveLock 对象,问题便得到解决了,NSRecursiveLock 类定义的锁可以在同一线程多次 lock,而不会造成死锁。递归锁会跟踪它被多少次 lock。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
NSConditionLock 条件锁
- 当我们在使用多线程的时候,有时一把只会 lock 和 unlock 的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁
- NSConditionLock 是锁,一旦一个线程获得锁,其他线程一定等待
- [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁
- [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
- [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
- return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
- test:
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger HAS_DATA = 1;
NSInteger NO_DATA = 0;
NSMutableArray *products = [NSMutableArray array];
NSConditionLock *lock = [[NSConditionLock alloc]initWithCondition:NO_DATA];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
sleep(0.1); //让线程2先执行
[lock lockWhenCondition:NO_DATA];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product,总量:%zi",products.count);
[lock unlockWithCondition:HAS_DATA];
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
NSLog(@"wait for product");
[lock lockWhenCondition:HAS_DATA];
[products removeAllObjects];
NSLog(@"custome a product");
[lock unlockWithCondition:NO_DATA];
}
});
}
output:
2016-07-19 12:22:26.299 test2[16530:2209739] wait for product
2016-07-19 12:22:26.299 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.374 test2[16530:2209739] custome a product
2016-07-19 12:22:26.375 test2[16530:2209739] wait for product
2016-07-19 12:22:26.375 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.376 test2[16530:2209739] custome a product
2016-07-19 12:22:26.376 test2[16530:2209739] wait for product
2016-07-19 12:22:26.376 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.377 test2[16530:2209739] custome a product
...
NSCondition
- 一种 最基本的条件锁。手动控制线程wait和signal。
- [condition lock];一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问
- [condition unlock]与lock 同时使用
- condition wait];让当前线程处于等待状态
- [condition signal];CPU发信号告诉线程不用在等待,可以继续执行
- test
- (void)viewDidLoad {
[super viewDidLoad];
NSCondition *condition = [[NSCondition alloc] init];
NSMutableArray *products = [NSMutableArray array];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
[condition lock];
if ([products count] == 0) {
NSLog(@"wait for product");
[condition wait];
}
[products removeObjectAtIndex:0];
NSLog(@"custome a product");
[condition unlock];
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (1) {
sleep(1);// 让线程1线先执行
[condition lock];
[products addObject:[[NSObject alloc] init]];
NSLog(@"produce a product,总量:%zi",products.count);
[condition signal];
sleep(5);
NSLog(@"1");//测试NSlog先执行还是线程1
[condition unlock];
}
});
}
output:
2016-07-19 14:30:39.360 test2[16886:2307736] wait for product
2016-07-19 14:30:40.362 test2[16886:2307733] produce a product,总量:1
2016-07-19 14:30:45.366 test2[16886:2307733] 1
2016-07-19 14:30:45.367 test2[16886:2307736] custome a product
2016-07-19 14:30:45.367 test2[16886:2307736] wait for product
2016-07-19 14:30:46.369 test2[16886:2307733] produce a product,总量:1
2016-07-19 14:30:51.374 test2[16886:2307733] 1
2016-07-19 14:30:51.374 test2[16886:2307736] custome a product
2016-07-19 14:30:51.375 test2[16886:2307736] wait for product
2016-07-19 14:30:52.376 test2[16886:2307733] produce a product,总量:1
OSSpinLock
- OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务
- test
#import "ViewController.h"
#import <libkern/OSAtomic.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"hello world 开始");
sleep(3);
NSLog(@"hello world 结束");
OSSpinLockUnlock(&theLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
OSSpinLockLock(&theLock);
NSLog(@"see goodbye");
OSSpinLockUnlock(&theLock);
});
}
@end
output:
2016-07-19 15:07:13.985 test2[17294:2334857] hello world 开始
2016-07-19 15:07:16.986 test2[17294:2334857] hello world 结束
2016-07-19 15:07:16.988 test2[17294:2334858] see goodbye
pthread_mutex(recursive)]
- pthread_mutex锁的递归版本
- test :
#import "ViewController.h"
#import <pthread/pthread.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block pthread_mutex_t theLock;
//pthread_mutex_init(&theLock, NULL);
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&theLock, &attr);
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
pthread_mutex_lock(&theLock);
if (value > 0) {
NSLog(@"value = %d", value);
sleep(1);
RecursiveMethod(value - 1);
}
pthread_mutex_unlock(&theLock);
};
RecursiveMethod(5);
});
}
@end
output:
2016-07-19 15:13:43.205 test2[17304:2339141] value = 4
2016-07-19 15:13:44.208 test2[17304:2339141] value = 3
2016-07-19 15:13:45.213 test2[17304:2339141] value = 2
2016-07-19 15:13:46.218 test2[17304:2339141] value = 1
NSDistributedLock 分布式锁
- 个时候我们就需要使用到 NSDistributedLock 了,从它的类名就知道这是一个分布式的 Lock,NSDistributedLock 的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但 NSDistributedLock 并非继承于 NSLock,它没有 lock 方法,它只实现了 tryLock, unlock, breakLock,所以如果需要 lock 的话,你就必须自己实现一个 tryLock
- 暂时没遇到,以后补充实例用法
效率
-
除了
OSSpinLock
外,dispatch_semaphore
和 pthread_mutex 性能是最高的,但是OSSpinLock不安全,dispatch_semaphore 不能递归,@synchronized和NSConditionLock效率较差 -
图个方便的可以使用@synchronized