@synchronized
@synchronized()是在OC代码中创建一个互斥锁非常方便的方法。@synchronized
指令做和其他互斥锁一样的工作(它防止不同的线程在同一时间获取同一个锁)。然而在这种情况下,你不需要直接创建一个互斥锁或锁对象。相反,你只需要简单的使用OC对象作为锁的令牌,如下面例子所示:
- (void)myMethod:(id)anObj {
@synchronized(anObj) {
// Everything between the braces is protected by the @synchronized directive.
}
}
@synchronized()的参数是一个对象,是用来区别保护块的唯一标示符。如果你在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给anObj参数,那么每次都将会拥有它的锁,并持续处理,中间不被其他线程阻塞。然而,如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其
他线程将会被阻塞直到第一个线程完成它的临界区。
具体的,编译器编译@synchronized包围的代码时,会把这段代码编译成
objc_sync_enter(obj);
....
objc_sync_exit(obj);
这2个函数的原型,就是
int objc_sync_enter(id obj);
int objc_sync_exit(id obj);
另外因为@synchronized,会确保它包围的代码,如果抛出异常等,也会调用objc_sync_exit
等,可能会稍有些性能上的损失。
@synchronized是执行效率最低,但最简单的一个directive。建议使用NSLock或者OSSpinLock。
NSLock
基本用法:
[lock lock];
[obj yourMethod];
[lock unlock];
执行原理:
- 某个线程A调用lock方法。这样,NSLock将被上锁。可以执行
yourMethod
,完成后,调用unlock方法。 - 如果,在线程A 调用unlock方法之前,另一个线程B调用了同一锁对象的lock方法。那么,线程B只有等待。直到线程A调用了unlock。
相关方法:
- (BOOL)lockBeforeDate:(NSDate *)limit
在指定的时间以前得到锁。YES:在指定时间之前获得了锁;NO:在指定时间之前没有获得锁。该线程将被阻塞,直到获得了锁,或者指定时间过期。
- (BOOL)tryLock
试图得到一个锁。YES:成功得到锁;NO:没有得到锁。
NSCondition
使用NSCondition,实现多线程的同步,即可实现生产者消费者问题。
问题流程如下:
- 消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
- 生产者制造产品,首先也要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。
这里需要注意wait和signal的问题:
- 其实,wait函数内部悄悄的调用了unlock函数(猜测,有兴趣可自行分析),也就是说在调用wati函数后,这个NSCondition对象就处于了无锁的状态,这样其他线程就可以对此对象加锁并触发该NSCondition对象。当NSCondition被其他线程触发时,在wait函数内部得到此事件被触发的通知,然后对此事件重新调用lock函数(猜测),而在外部看起来好像接收事件的线程(调用wait的线程)从来没有放开NSCondition对象的所有权,wati线程直接由阻塞状态进入了触发状态一样。这里容易造成误解。
- wait函数并不是完全可信的。也就是说wait返回后,并不代表对应的事件一定被触发了,因此,为了保证线程之间的同步关系,使用NSCondtion时往往需要加入一个额外的变量来对非正常的wait返回进行规避。
- 关于多个wait时的调用顺序,测试发现与wait执行顺序有关。具体请查阅文档。
实例如下:
- (void)producterAndConsumer {
NSLog(@"begin condition works!");
_products = [[NSMutableArray alloc] init];
_condition = [[NSCondition alloc] init];
NSThread *consumer = [[NSThread alloc] initWithTarget:self selector:@selector(createConsumer) object:nil];
[consumer setName:@"consumer"];
[consumer start];
NSThread *producter = [[NSThread alloc] initWithTarget:self selector:@selector(createProducter) object:nil];
[producter setName:@"p-0"];
[producter start];
NSThread *producter1 = [[NSThread alloc] initWithTarget:self selector:@selector(createProducter) object:nil];
[producter1 setName:@"p-1"];
[producter1 start];
}
- (void)createProducter {
while (TRUE) {
[_condition lock];
while (_products.count == 10) {
[_condition wait];
}
[NSThread sleepForTimeInterval:1];
[_products addObject:[NSObject new]];
//唤醒在此NSCondition对象上等待的单个线程 (通知消费者进行消费)
[_condition signal];
[_condition unlock];
}
}
- (void)createConsumer {
while (TRUE) {
[_condition lock];
while (_products.count == 0) {
[_condition wait];
}
[_products removeLastObject];
//唤醒在此NSCondition对象上等待的单个线程 (通知生产者进行生产)
[_condition signal];
[_condition unlock];
}
}
NSConditionLock 条件锁
NSConditionLock
定义了一个条件互斥锁,也就是当条件成立时就会获取到锁,反之就会释放锁。因为这个特性,条件锁可以被用在有特定顺序的处理流程中,比如生产者-消费者问题。
- (void)producterAndConsumerWithConditionLock
{
_products = [[NSMutableArray alloc] init];
_condition = [[NSCondition alloc] init];
NSThread *consumer = [[NSThread alloc] initWithTarget:self selector:@selector(consumer) object:nil];
[consumer setName:@"consumer"];
[consumer start];
NSThread *producter = [[NSThread alloc] initWithTarget:self selector:@selector(producter) object:nil];
[producter setName:@"p-0"];
[producter start];
NSThread *producter1 = [[NSThread alloc] initWithTarget:self selector:@selector(producter) object:nil];
[producter1 setName:@"p-1"];
[producter1 start];
}
- (void)producter {
while (TRUE) {
NSLog(@"producter lock");
[_conditionLock lock];
[NSThread sleepForTimeInterval:0.5];
[_products addObject:[[NSObject alloc] init]];
NSLog(@"%@生产了一个产品,库房总数是%ld", [NSThread currentThread].name, _products.count);
//唤醒在此NSCondition对象上等待的单个线程 (通知消费者进行消费)
int count = (int)_products.count;
[_conditionLock unlockWithCondition:count];
}
}
- (void)consumer {
while (TRUE) {
NSLog(@"consumer lock");
[_conditionLock lockWhenCondition:10];
for (NSUInteger i=_products.count; i>0; i--) {
[NSThread sleepForTimeInterval:0.1];
[_products removeLastObject];
NSLog(@"消费了一个产品,库房总数是%ld", _products.count);
}
//唤醒在此NSCondition对象上等待的单个线程 (通知生产者进行生产)
[_conditionLock unlock];
}
}
NSConditionLock 相关知识点:
- [_conditionLock lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
- [_conditionLock unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
- return = [_conditionLock lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
- 所谓的condition就是整数,内部通过整数比较条件
NSRecursiveLock 递归锁
NSRecursiveLock
定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。主要用在循环或递归操作中。
- (void)recursiveLockMethod:(int)num
{
[_recursiveLock lock];
if (num > 0) {
NSLog(@"num = %d", num);
sleep(2);
[self recursiveLockMethod:num-1];
}
NSLog(@"resursiveLock unlock");
[_recursiveLock unlock];
}
NSDistributedLock 分布式锁
以上所有的锁都是在解决多线程之间的冲突,但如果遇上多个进程或多个程序之间需要构建互斥的情景该怎么办呢?这个时候我们就需要使用到NSDistributedLock了,从它的类名就知道这是一个分布式的Lock,NSDistributedLock的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但NSDistributedLock并非继承于NSLock,它没有lock方法,它只实现了tryLock,unlock,breakLock,所以如果需要lock的话,你就必须自己实现一个tryLock的轮询,下面通过代码简单的演示一下吧:
程序A:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];
[lock breakLock];
[lock tryLock];
sleep(10);
[lock unlock];
NSLog(@"appA: OK");
});
程序B:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/earning__"];
while (![lock tryLock]) {
NSLog(@"appB: waiting");
sleep(1);
}
[lock unlock];
NSLog(@"appB: OK");
});
先运行程序A,然后立即运行程序B,根据打印你可以清楚的发现,当程序A刚运行的时候,程序B一直处于等待中,当大概10秒过后,程序B便打印出了appB:OK的输出,以上便实现了两上不同程序之间的互斥。/Users/mac/Desktop/earning__
是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在tryLock返回YES时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。
dispatch_semaphore 信号量实现加锁
dispatch_semaphore 信号量基于计数器的一种多线程同步机制。在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArrayarray];
for (int index = 0; index < 100000; index++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^(){
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
dispatch_semaphore_signal(semaphore);
});
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
如果semaphore计数大于等于1,将semaphore减1返回,程序继续运行。如果semaphore计数为0,则等待,此处设置的等待时间是一直等待。 dispatch_semaphore_signal(semaphore)
将semaphore计数+1。在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。
OSSpinLock 自旋锁
iOS/MacOS自有的自旋锁,其特点是线程等待取锁时不进内核,线程因此不挂起,直接保持空转,这使得它的锁操作开销降得很低,OSSpinLock是不支持递归的。
pthread_mutex
POSIX标准的unix多线程库(pthread)中使用的互斥量,支持递归,需要特别说明的是信号机制pthread_cond_wait()同步方式也是依赖于该互斥量,pthread_cond_wait()本身并不具备同步能力。
atomic(property) set/get
利用set/get接口的属性实现原子操作,进而确保“被共享”的变量在多线程中读写安全,这已经是能满足部分多线程同步要求。
参考链接:起底多线程同步锁(iOS)