我正在Swift 3中创建一个服务器端应用程序。我选择libevent来实现网络代码,因为它是跨平台的,并且不会遭受C10k问题的困扰。 Libevent实现了它自己的事件循环,但是我也想保持CFRunLoop和GCD(DispatchQueue.main.after
等)的功能,所以我需要以某种方式粘合它们。
这是我想出的:
var terminated = false
DispatchQueue.main.after(when: DispatchTime.now() + 3) {
print("Dispatch works!")
terminated = true
}
while !terminated {
switch event_base_loop(eventBase, EVLOOP_NONBLOCK) { // libevent
case 1:
break // No events were processed
case 0:
print("DEBUG: Libevent processed one or more events")
default: // -1
print("Unhandled error in network backend")
exit(1)
}
RunLoop.current().run(mode: RunLoopMode.defaultRunLoopMode,
before: Date(timeIntervalSinceNow: 0.01))
}
这可行,但是引入了0.01秒的延迟。当RunLoop处于 sleep 状态时,libevent将无法处理事件。降低此超时时间可在应用程序空闲时显着增加CPU使用率。
我也在考虑仅使用libevent,但是项目中的第三方库可以在内部使用dispatch_async,因此这可能会出现问题。
在另一个线程中运行libevent的循环会使同步更加复杂,这是解决此延迟问题的唯一方法吗?
LINUX更新。 上面的代码在Linux上不起作用(2016-07-25-a Swift快照),
RunLoop.current().run
存在错误。下面是使用计时器和dispatch_main
重新实现的有效Linux版本。它遭受相同的延迟问题:let queue = dispatch_get_main_queue()
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
let interval = 0.01
let block: () -> () = {
guard !terminated else {
print("Quitting")
exit(0)
}
switch server.loop() {
case 1: break // Just idling
case 0: break //print("Libevent: processed event(s)")
default: // -1
print("Unhandled error in network backend")
exit(1)
}
}
block()
let fireTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
dispatch_source_set_timer(timer, fireTime, UInt64(interval * Double(NSEC_PER_SEC)), UInt64(NSEC_PER_SEC) / 10)
dispatch_source_set_event_handler(timer, block)
dispatch_resume(timer)
dispatch_main()
最佳答案
快速搜索GitHub上的Open Source Swift Foundation库发现,(也许很明显)在不同平台上实现CFRunLoop
的支持是不同的。从本质上讲,这意味着就跨平台性而言,RunLoop
和libevent
是实现同一件事的不同方法。我可以看到libevent
可能更适合服务器实现的想法背后的想法,因为CFRunLoop
并没有达到特定的目标,但是就跨平台而言,它们都在同一棵树上。
就是说,RunLoop
和libevent
使用的底层同步原语本质上是私有(private)实现细节,也许更重要的是,平台之间也有所不同。从源头上看,RunLoop
和epoll
一样在Linux上使用libevent
,但是在macOS/iOS/etc上,RunLoop
将使用Mach端口作为其基本基元,但是libevent
似乎将使用kqueue
。您可能会花足够的精力来制作与给定平台的RunLoopSource
源绑定(bind)的混合libevent
,但这可能很脆弱,并且出于以下几个原因通常是不明智的:基于RunLoop
的私有(private)实现细节(不属于公共(public)API),因此随时可能更改,恕不另行通知。其次,假设您没有针对Swift和libevent
支持的每个平台进行操作,那么您将破坏它的跨平台性,这是您首先声明使用libevent
的理由之一。
您可能没有考虑的另一种选择是单独使用GCD,而无需使用RunLoops
。查看 dispatch_main
的文档。在服务器应用程序中,(通常)“主线程”没有什么特别的,因此分派(dispatch)到“主队列”应该足够好(如果需要的话)。您可以使用调度“源”来管理您的连接等。我个人不能说调度源如何扩展到C10K/C100K/etc。级别,但以我的经验来看,它们似乎非常轻巧且开销低。我还怀疑这样使用GCD可能是在Swift中编写服务器应用程序的最惯用的方式。我已将another answer here的一部分写成一个基于GCD的TCP回显服务器的小示例。
如果您被束缚并决心在同一应用程序中同时使用RunLoop
和libevent
,那么,正如您所猜测的那样,最好将libevent
赋予它自己的单独线程,但是我认为它没有您想象的那么复杂。您应该能够从dispatch_async
回调中自由地进行libevent
,并且类似地使用libevent
的多线程机制将来自GCD管理线程的回复编码为libevent
(即通过锁定运行或将事件本身编码为libevent
的调用) 。)同样,即使您选择使用libevent的循环结构,使用GCD的第三方库也不应该成为问题。 GCD管理自己的线程池,无法踩到libevent
的主循环等。
您可能还会考虑对应用程序进行架构设计,以至于使用哪种并发和连接处理库都没有关系。然后,您可以换出libevent
,GCD,CFStreams等(或混合搭配),这取决于在特定情况下或部署中最有效的方法。选择并发方法很重要,但是理想情况下,您不会将自己与之紧密地联系在一起,以至于在情况需要时您无法切换。
当您拥有这样的体系结构时,我通常会喜欢使用可以完成工作的最高级别抽象的方法,并且仅在特定情况需要时才使用较低级别的抽象。在这种情况下,这可能意味着要使用CFStreams
和RunLoops
来启动,然后在切换到“裸” GCD或libevent
时,如果碰到一堵墙并且还(通过经验测量)确定它是传输层而不是应用程序,则可能意味着层是限制因素。实际上,很少有平凡的应用程序能够解决传输层中的C10K问题。事情往往必须首先在应用程序层“扩展”,至少对于比基本消息传递更复杂的应用程序而言。
关于swift - 在Swift中结合使用libevent和GCD(libdispatch),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38425707/