在上家公司遇到了好几次多线程崩溃导致的Case,故学习多线程容易崩溃的地方,笔记:
崩溃点
1.released twice:
多个线程同时访问set方法,可能导致被set的对象的多次释放
@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];
});
}
- (void)setUserName:(NSString *)userName {
if (userName != _userName) {
[userName retain];
[_userName release];
_userName = userName;
}
}
2.读写时序有问题
逻辑上读写以不恰当的时序发生了(1)同时(2)不该发生的读先于写
3.在非主线程更新UI
查了一堆资料,Apple文档也只说了“Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.”
我对于这些问题的理解:
UIKit相关的一切都是在main thread上处理的,子线程更新UI(访问某个子view的内存)的时候,可能主线程也在访问那块内存(比如拿subviews出来,一个一个绘制),可能正好撞在一起。所以更新UI的操作派发到串行的主线程就不存在可能跟老大哥撞车的问题了。
处理办法:
1.属性级别的线程安全:属性原子性
官方自旋锁实现属性的原子性
static inline void reallySetProperty(id self, SEL _cmd, id newValue,
ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *slot;
*slot = newValue;
_spin_unlock(slotlock);
}
objc_release(oldValue);
}
2.抛出问题
该在某一线程的操作没在该线程可以直接在开发期抛出错误:
void PSPDFAssertIfNotMainThread(void) {
NSAssert(NSThread.isMainThread,
@"Error: Method needs to be called on the main thread. %@",
[NSThread callStackSymbols]);
}
3.逻辑级别的线程安全:concurrent dispatch_queue VS @synchronized
concurrent dispatch_queue:
// header
@property (nonatomic, strong) NSMutableSet *delegates;
// in init
_delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue",
DISPATCH_QUEUE_CONCURRENT);
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
dispatch_barrier_async(_delegateQueue, ^{
[self.delegates addObject:delegate];
});
}
- (void)removeAllDelegates {
dispatch_barrier_async(_delegateQueue, ^{
self.delegates removeAllObjects];
});
}
- (void)callDelegateForX {
dispatch_sync(_delegateQueue, ^{
[self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) {
// Call delegate
}];
});
}
如果发生次数不不是一秒几千次可以用@synchronized(有异常抛出处理所以慢一点):
// header
@property (atomic, copy) NSSet *delegates;
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
@synchronized(self) {
self.delegates = [self.delegates setByAddingObject:delegate];
}
}
- (void)removeAllDelegates {
@synchronized(self) {
self.delegates = nil;
}
}
- (void)callDelegateForX {
[self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) {
// Call delegate
}];
}
4.自己设计的数据结构可以在修改方法中创造不可变copy(写放提供不可变copy)
5.不要这样把有时序问题的更新放在下一runloop:
dispatch_async(dispatch_get_main_queue(), ^{
// Some UIKit call that had timing issues but works fine
// in the next runloop.
[self updatePopoverSize];
});
延迟派发的更新越多,越可能出现新的时序问题,不如找准代码应该执行的位置如:viewdidload-》viewwillappear
6.自己Subclass NSOperation可以打印更多的调试信息
参考文章
网友评论