Dictionary、String 是 swift 标准库中的值类型数据结构,单从使用层面上而论,相信铁子们都可以不假思索、信手拈来。但如果在某种使用条件下,发生了运行时崩溃,阁下又将如何应对?
如果你看“题干”的话,就自然而然地想到这个情况就是多线程下数据一致性问题。至于如何保护临界区数据不在这篇文章的讨论范围,但我们不妨深入 Swift 源码,去揭开崩溃原因的神秘面纱~
Dictionry
倒数五个数,三、二、一,上 🌰:
1 2 3 4 5 6 7 8 9 10 11
funccrashOfRaceConditionInDictionary() { var dict: [String: Double] = [:] let key ="ABCD" DispatchQueue.concurrentPerform(iterations: 2) { index in if index %2==0 { print(dict[key] asAny) } else { dict[key] =Double(index) } } }
目前已知例程中会出现下面两种崩溃的情况:
EXC_BAD_ACCESS
[XXXX objectForKey:] unrecognized selector sent to instance XXXXXX
funccrashOfRaceConditionInString() { var string =""
for_in0..<50 { DispatchQueue.concurrentPerform(iterations: 4) { index in if index %2==0 { string +="ABC" } else { string +="DEF" } } } }
这次的崩溃 Xcode 给出了很友好的控制台输出:
Object 0xXXX of class __StringStorage deallocated with non-zero retain count 2. This object’s deinit, or something called from it, may have created a strong reference to self which outlived deinit, resulting in a dangling reference.
// Ensure unique native storage with sufficient capacity for the following // append. privatemutatingfuncprepareForAppendInPlace( totalCount: Int, otherUTF8CountotherCount: Int ) { // See if we can accommodate without growing or copying. If we have // sufficient capacity, we do not need to grow, and we can skip the copy if // unique. Otherwise, growth is required.
//......
// If we have to resize anyway, and we fit in smol, we should have made one //......
// Non-unique storage: just make a copy of the appropriate size, otherwise // grow like an array. let growthTarget: Int if sufficientCapacity { growthTarget = totalCount } else { growthTarget =Swift.max( totalCount, _growArrayCapacity(nativeCapacity ??0)) } self.grow(growthTarget) // NOTE: this already has exponential growth... }
// constant propagation will remove this in swift_release, it should only // be present in swift_release_n if (dec !=1&& oldbits.isImmortal(true)) { returnfalse; }
bool deinitNow; do { newbits = oldbits;
bool fast = newbits.decrementStrongExtraRefCount(dec); if (fast) { // Decrement completed normally. New refcount is not zero. deinitNow =false; } elseif (oldbits.isImmortal(false)) { returnfalse; } elseif (oldbits.hasSideTable()) { // Decrement failed because we're on some other slow path. return doDecrementSideTable<performDeinit>(oldbits, dec); } else { // Decrement underflowed. Begin deinit. // LIVE -> DEINITING deinitNow =true; assert(!oldbits.getIsDeiniting()); // FIXME: make this an error? newbits = oldbits; // Undo failed decrement of newbits. newbits.setStrongExtraRefCount(0); newbits.setIsDeiniting(true); } } while (!refCounts.compare_exchange_weak(oldbits, newbits, std::memory_order_release, std::memory_order_relaxed)); if (performDeinit && deinitNow) { std::atomic_thread_fence(std::memory_order_acquire); _swift_release_dealloc(getHeapObject()); }