首页 > 解决方案 > 对于数组的本地副本,Swift Array 排序方法线程安全吗?

问题描述

我的应用程序在下面第 2 行的排序过程中遇到了崩溃。这发生在一个方法中,ViewController当 aNSNotification进入以触发排序并刷新显示时。

我的理解是,第devices1 行的数组是从字典创建的新数组,因此即使字典可能会更改,访问元素也是安全的。

即使serialID(a String?) 在排序过程中发生变化,我也不认为这会导致崩溃。

我错过了什么?什么可能导致排序内部的错误访问?

1: var devices = deviceDict.valuesArray() // See Edit below
2: let sortedDevices = devices.sorted(by: { ($0.serialID ?? "") < ($1.serialID ?? "") })

我没有已知的方法来重现这个。我只在下面的崩溃报告中看到过它。

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000f9a70f7c0
VM Region Info: 0xf9a70f7c0 is not in any region.  Bytes after previous region: 55741315009  Bytes before following region: 630130752
      REGION TYPE                 START - END      [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      MALLOC_NANO              280000000-2a0000000 [512.0M] rw-/rwx SM=COW  
--->  GAP OF 0xd20000000 BYTES
      commpage (reserved)      fc0000000-1000000000 [  1.0G] ---/--- SM=NUL  ...(unallocated)

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [4510]
Triggered by Thread:  9

...
  
  Thread 9 Crashed:
0   libobjc.A.dylib                 0x00000001a6ae2dd0 objc_release + 16 (objc-runtime-new.h:1589)
1   OAPP                            0x00000001044c35e4 specialized UnsafeMutableBufferPointer._stableSortImpl(by:) + 364 (DeviceViewController.swift:0)
2   OAPP                            0x00000001044c17f0 specialized MutableCollection<>.sort(by:) + 100
2   OAPP                            0x00000001044baf60 DeviceViewController.generateDeviceList() + 1644
4   OAPP                            0x00000001044b67ac DeviceViewController.refreshDeviceTableView() + 524 (DeviceViewController.swift:655)
5   OAPP                            0x00000001044bb7a4 DeviceViewController.handleSyncStateNotification(_:) + 680 (DeviceViewController.swift:821)
...

编辑deviceDict实际上是在一个自定义类上,该类包装了一些线程安全访问器的字典,我将字典值转换为该类中的数组。(在我原来的帖子中,我试图简化代码,以免分散对数组操作的崩溃问题的注意力,但我似乎因为简化而混淆了事情。对不起!)

public class SafeDict<Element> {
    fileprivate let queue = DispatchQueue(label: "com.myapp.SafeDict", attributes: .concurrent)
    fileprivate var dictionary:[String:Element] = [:] 
...
    public func valuesSafe() -> Dictionary<String, Element>.Values {
        var returnValues: Dictionary<String, Element>.Values = Dictionary<String, Element>().values
        queue.sync {
            returnValues = self.dictionary.values
        }
        return returnValues
    }
    
    public func valuesArray() -> [Element] {
        var values: [Element] = []
        queue.sync {
            for element in self.valuesSafe() {
                values.append(element)
            }
        }
        return values
    }
}

标签: iosarraysswiftsortingthread-safety

解决方案


devices不是数组。这是一个Dictionary.Values。这是对 deviceDict 的惰性视图。

但这些都不重要。在多个线程上对 deviceDict 的可变访问始终是未定义的行为。如果可以serialID在没有某种同步的情况下在另一个线程上进行更改,那么您已经犯了一个错误。该deviceDict.values()行将无效;与以后访问它无关。

但当然,这sorted条线更有可能是那种未定义的行为咬你的地方,因为它会遍历很多值,如果deviceDict必须调整大小或以其他方式修改其后备存储,这些值可能会在内存中移动。


推荐阅读