Swift 数组排序方法对于数组的本地副本是线程安全的吗?

Is the Swift Array sorted method thread safe for a local copy of an array?

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

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

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

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

1: var devices = deviceDict.valuesArray() // See Edit below
2: let sortedDevices = devices.sorted(by: { ([=11=].serialID ?? "") < (.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)
...

EDITdeviceDict 实际上是在自定义 class 上,它包装了一些线程安全访问器的字典,我将字典值转换为class 中的数组。 (在我原来的 post 中,我试图简化代码,以免分散对数组操作崩溃问题的注意力,但我似乎通过简化使事情变得混乱。抱歉!)

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
    }
}

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

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

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