我正在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的支持是不同的。从本质上讲,这意味着就跨平台性而言,RunLooplibevent是实现同一件事的不同方法。我可以看到libevent可能更适合服务器实现的想法背后的想法,因为CFRunLoop并没有达到特定的目标,但是就跨平台而言,它们都在同一棵树上。

就是说,RunLooplibevent使用的底层同步原语本质上是私有(private)实现细节,也许更重要的是,平台之间也有所不同。从源头上看,RunLoopepoll一样在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回显服务器的小示例。

如果您被束缚并决心在同一应用程序中同时使用RunLooplibevent,那么,正如您所猜测的那样,最好将libevent赋予它自己的单独线程,但是我认为它没有您想象的那么复杂。您应该能够从dispatch_async回调中自由地进行libevent,并且类似地使用libevent的多线程机制将来自GCD管理线程的回复编码为libevent(即通过锁定运行或将事件本身编码为libevent的调用) 。)同样,即使您选择使用libevent的循环结构,使用GCD的第三方库也不应该成为问题。 GCD管理自己的线程池,无法踩到libevent的主循环等。

您可能还会考虑对应用程序进行架构设计,以至于使用哪种并发和连接处理库都没有关系。然后,您可以换出libevent,GCD,CFStreams等(或混合搭配),这取决于在特定情况下或部署中最有效的方法。选择并发方法很重要,但是理想情况下,您不会将自己与之紧密地联系在一起,以至于在情况需要时您无法切换。

当您拥有这样的体系结构时,我通常会喜欢使用可以完成工作的最高级别抽象的方法,并且仅在特定情况需要时才使用较低级别的抽象。在这种情况下,这可能意味着要使用CFStreamsRunLoops来启动,然后在切换到“裸” GCD或libevent时,如果碰到一堵墙并且还(通过经验测量)确定它是传输层而不是应用程序,则可能意味着层是限制因素。实际上,很少有平凡的应用程序能够解决传输层中的C10K问题。事情往往必须首先在应用程序层“扩展”,至少对于比基本消息传递更复杂的应用程序而言。

关于swift - 在Swift中结合使用libevent和GCD(libdispatch),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38425707/

10-09 02:17