在上家公司遇到了好幾次多線程崩潰導致的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可以打印更多的調試信息
參考文章