iOS开发-多线程安全问题

今天对项目进行了一些优化,想要把数据处理全部放在一个异步并发的队列中处理,写完代码之后发现偶尔会出现崩溃,原因就是EXC_BAD_ACCESS错误。

在网上也找到了类似的问题,有一个网易面试题:

1
2
3
4
5
6
7
8
@property (nonatomic, strong) NSString *target;
//....
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++)
{
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});

这个其实就是我写的代码的一种直接表现的版本,其实就是::对已经释放的内存的对象再次发送消息出现的::。

原因

问题的原因是因为我们现在使用ARC编码,但是如果对于这个对象来说,它的setter方法在MRC环境下就是

1
2
3
4
5
-(void)setTarget:(NSString *)target
{
[target retain];//先保留新值
[_target release];//再释放旧值
_target = target;//再进行赋值

而调用了问题的原因就是就是因为我们使用了异步+并发队列处理数据,也就是加入队列A执行到第二行代码,还没有执行到第三行时,队列B也执行到第二行代码,这时候队列A执行第三行代码之后就会释放对象,然后队列B使用的时候也会释放,就会造成过度释放。

解决

知道了造成问题的原因,那么解决的办法也就大概知道了。

改用串行队列

过度释放release的问题就是并发队列多次同时访问了资源,那么使用串行队列就可以解决这个问题了。主要是在setter方法中改用串行队列。

使用atomic(加锁)

我们正常初始化变量的时候一般都是使用nonatomic,也就是非原子性,而使用atomic为原子性就相当于给setter方法加锁。atomic也只是对于setter方法有用,而其他情况时需要使用其他加速方式。

使用weak

Weak关键字的setter没有保留新的值或者保留旧值的操作。所以不会引起重复释放。但是并不是所有变量都适合使用weak。

参考文档

苹果开发中文网站从一道网易面试题浅谈OC线程安全 - CocoaChina_让移动开发更简单
iOS多线程到底不安全在哪里?
我所理解的 iOS 并发编程 - 掘金
iOS多线程经典崩溃 - 简书