读effective objective-c 2.0 整理文章 - 41条 多用派发队列,少用锁

问题引入:在Objective-C中,如果有两个线程执行同一份代码,那么可能会出现问题

传统方案:  用锁来实现同步机制

方法一:同步块 @synchronized(self)

              -(void)synchronizedMethod{

@synchronized(self){

//safe

}

}

这种方法会根据给定对象,创建一个锁,并等待块中得代码执行完毕。执行到这段代码结束,锁就释放了。

劣处:@synchronized(self)可以保证每个对象实例都不受干扰的运行其synchronizedMethod方法,但是会降低代码效率,因为共用用一个锁的那些块都必须顺序执行,若是在self对象上频繁枷锁,那么程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码。


方法二:NSlock或者NSRecurisiveLock

_lock = [NSlock alloc] init];

-(void) synchronizedMethod{

[_lock lock];

//safe

[_lock unlock];

}

劣处:在极端情况下,同步块出现死锁,效率也不高


方法三: 属性的原子性

原子性会是编译器在生成setter和getter方法时加上锁定机制,原理如同方法一

-(NSString*) someString{

@synchronized(self)

return _someString;

}

滥用@synchronized(self)会很危险,因为所有同步块都会彼此抢夺同一个锁。要是有很多属性都这么写,那么每个属性的同步块都要等待其他所有同步块执行完毕才能执行。而我们只是想令每个属性各自独立地同步。

所以默认属性使用noatmic可以提高效率

顺便说一下:这么做虽然提供了某种程度的安全,却无法保证访问该对象时绝对线程安全。 当然访问属性操作确实是原子性的。如果在同一个线程上多次调用getter,每次获取到的结果未必相同。 在两次访问之间,其他线程可能会写入新的属性值。


建议使用GCD处理以上提出的问题

方法一:串行同步队列—将对去操作和写入操作都安排在用一个队列里

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);

-(NSString*) someString{

__block NSString* localString;

dispatch_sync(_syncQueue,^{

localString = _someString;

});

return localString

}

-(void)setSomeString:(NSString*)someString {

dispatch_sync(_syncQueue,^{

_someString = someString;

});

}

这样做全部加锁任务都在GCD中处理,而GCD是在相当深的底层来实现的,于是能够做许多优化。

对以上方法做第一步的优化方案

-(void)setSomeString:(NSString*)someString {

dispatch_async(_syncQueue,^{

_someString = someString;

});

}

dispatch_sync --》 dispatch_async

设置方法改成异步分发,从调用者的角度可以提升设置方法的执行速度,而读取操作和写入操作依然是顺序执行的。但这种方法对于块中代码很简洁的情况反而降低了效率,因为执行异步分法时,需要拷贝块若拷贝块所用的时间超过了块的执行时间,那么这样做反而会使效率降低。然而如果块中执行任务比较繁琐,这种该法还是可以提高效率的。 此方法仍然不能解决 “如果在同一个线程上多次调用getter,每次获取到的结果未必相同。 在两次访问之间,其他线程可能会写入新的属性值。”这种情况


为了提高效率还有更优化的方案

多个获取方法可以并行执行,而获取方法和设置方法之间不能并行执行,利用这个特点,可以写出效率更高的代码来。此时更能体现出GCD的优越性

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

-(NSString*) someString{

__block NSString* localString;

dispatch_sync(_syncQueue,^{

localString = _someString;

});

return localString

}

-(void)setSomeString:(NSString*)someString {

dispatch_async(_syncQueue,^{

_someString = someString;

});

}

这样写无法实现同步。所有操作并发执行,所以读取和写入操作随意执行了

GCD中的barrier可以解决以上问题

将setter方法改成如下

-(void)setSomeString:(NSString*)someString {

dispatch_barrier_async(_syncQueue,^{

_someString = someString;

});

}

因为在队列中,barrier块必须单独执行,不能与其他块并行。这支队并行队列有意义。并行队列中如果发现接下来要处理得块是barrier块,那么要等待当前所有并发块都执行完毕才会单独执行这个barrier块。




发布了17 篇原创文章 · 获赞 6 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/jacky_jin/article/details/49304037
今日推荐