需要帮助来了解崩溃的真正原因。我已经制作了应该24/7正常运行的应用程序。它从设备读取并准备一些数据,并创建一个Web服务器以将数据发送到探针。 崩溃发生在各种时间之后,因此我无法真正在模拟器中重现它。
这是最后一个崩溃文件的详细信息:
自启动以来唤醒的时间:110000秒
系统完整性保护:已启用
崩溃的线程:37调度队列:com.apple.root.default-qos
异常类型:EXC_CRASH(SIGABRT)
异常代码:0x0000000000000000,0x0000000000000000
异常说明:EXC_CORPSE_NOTIFY
已达到调度线程软限制:64(在同步操作中阻塞了太多的调度线程)
特定于应用程序的信息: abort()称为
***对象0x247032000错误:未分配释放的指针
线程37的详细信息:
线程37崩溃::调度队列:com.apple.root.default-qos
0 libsystem_c.dylib 0x00007fff8dab3298 usleep $ NOCANCEL + 0
1 libsystem_c.dylib 0x00007fff8dae16e9中止+ 139
2 libsystem_malloc.dylib 0x00007fff965fe041免费+ 425
3 libswiftCore.dylib 0x0000000106fed219 $ Ss19_SwiftStringStorageCfD + 9
4 libswiftCore.dylib 0x0000000106ffba00 _swift_release_dealloc + 16
...
46 org.cocoapods.GCDWebServer 0x0000000106c67316-[GCDWebServerConnection(Subclassing)processRequest:completion:] + 128
47 org.cocoapods.GCDWebServer 0x0000000106c6392e-[GCDWebServerConnection _startProcessingRequest] + 146
48 org.cocoapods.GCDWebServer 0x0000000106c64e13 __45- [GCDWebServerConnection _readRequestHeaders] _block_invoke + 1781
49 org.cocoapods.GCDWebServer 0x0000000106c65935 __64- [GCDWebServerConnection(Read)readHeaders:withCompletionBlock:] _ block_invoke + 290
50 org.cocoapods.GCDWebServer 0x0000000106c65613 __68- [GCDWebServerConnection(Read)readData:withLength:completionBlock:] _ block_invoke + 307
51 libdispatch.dylib 0x00007fff8e73c7a8 __dispatch_read_block_invoke_252 + 39
52 libdispatch.dylib 0x00007fff8e72a93d _dispatch_call_block_and_release + 12
53 libdispatch.dylib 0x00007fff8e71f40b _dispatch_client_callout + 8
54 libdispatch.dylib 0x00007fff8e72329b _dispatch_root_queue_drain + 1890
55 libdispatch.dylib 0x00007fff8e722b00 _dispatch_worker_thread3 + 91
56 libsystem_pthread.dylib 0x00007fff8ea934de _pthread_wqthread + 1129
57 libsystem_pthread.dylib 0x00007fff8ea91341 start_wqthread + 13
代码:
这是处理Web服务器(GCDWebServer)的单调代码。它根据http查询中的ID读取存储在内存中的数据
private let queue = DispatchQueue(label: "gcdwebserver_queue")
private func setupServer(){
webServer.delegate = self
webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self) { (req, completion) in
if let resp = self.response(for: req) {
return completion(resp)
}
}
queue.async {
self.webServer.start(withPort: 8521, bonjourName: "GCD Web Server")
}
}
下面是一个单例代码,该单例调用modbus(C Modbus库)连接(每30秒)到设备列表并读取数据:
private let modbusQueue = DispatchQueue(label: "modbus_queue")
private func initiateTimer() {
polling()
timer?.invalidate()
if #available(OSX 10.12, *) {
timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true) { (_) in
self.polling()
}
} else {
timer = Timer.scheduledTimer(timeInterval: pollingInterval, target: self, selector: #selector(polling), userInfo: nil, repeats: true)
}
}
@objc private func polling() {
for device in self.devices {
if !device.isInProcess && device.isEnabled {
self.modbusQueue.async {
self.connectAndReadValues(device: device)
}
}
}
}
private func connectAndReadValues(device: Device) {
device.isInProcess = true
let connect = device.modbus.connect()
//handling connection status
//...
readValues(forDevice: device)
}
private func readValues(forDevice device: Device){
//some constants
do {
let registers = try device.modbus.readRegisters(from: startAddress, count: registersCount)
device.readState = .success
device.modbus.close()
//parse and save data to the app memory just as a dictionary. It saves only one small dictionary per device
parseRegisters(controllerIP: device.modbus.host, vendor: vendor, registers: registers, startAdd: startAddress)
} catch let error {
//handling errors
}
//refreshing interface in the main queue
}
答案 0 :(得分:1)
“已达到调度线程软限制:64”将是一个不错的起点。看来您正在耗尽工作线程池(只有64个)。
例如,如果modbusQueue
是并发队列,则以下内容很容易耗尽您的工作线程:
for device in self.devices {
if !device.isInProcess && device.isEnabled {
self.modbusQueue.async {
self.connectAndReadValues(device: device)
}
}
}
有一些解决方案:
您可以使用操作队列,并设置其maxConcurrentOperationCount
:
let queue = OperationQueue()
queue.name = "modbusQueue"
queue.maxConcurrentOperationCount = 4
for device in self.devices where !device.isInProcess && device.isEnabled {
queue.addOperation {
self.connectAndReadValues(device: device)
}
}
如果要保留在调度队列中,可以使用起始值非零的信号量来限制并发程度。常规模式是:
let semaphore = DispatchSemaphore(value: 4)
for object in self.objects {
semaphore.wait() // the first four will start immediately; the subsequent ones will wait for signals from prior ones
queue.async {
...
semaphore.signal() // signal that this is done
}
}
因此,这一次将尝试不超过四个。而且您可以将整个事件分派到某个后台队列中,因此在执行此操作时不会阻塞调用线程。无论如何,您最终得到:
DispatchQueue.global().async {
let semaphore = DispatchSemaphore(value: 4)
for device in self.devices where !device.isInProcess && device.isEnabled {
semaphore.wait()
self.modbusQueue.async {
self.connectAndReadValues(device: device)
semaphore.signal()
}
}
}
或者您可以使用concurrentPerform
:
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: devices.count) { index in
let device = self.devices[index]
if !device.isInProcess && device.isEnabled {
self.connectAndReadValues(device: device)
}
}
}
我可能倾向于操作队列模式,但这是一些选择。
FWIW,我会警惕使用计时器进行轮询。您可能要冒使队列严重积压的风险。例如,如果在定时器再次触发时未完成处理事件怎么办?
如果您确实必须进行轮询,则建议您删除计时器,而只需在队列末尾添加一个隔离项,以触发设备的下一次轮询。