在Swift中使用libevent和GCD(libdispatch)

Zme*_*mey 5 network-programming libevent grand-central-dispatch swift swift3

我正在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))
}
Run Code Online (Sandbox Code Playgroud)

这有效,但引入了0.01秒的延迟.当RunLoop正在休眠时,libevent将无法处理事件.当应用程序空闲时,降低此超时会显着增加CPU使用率.

我也在考虑只使用libevent,但是项目中的第三方库可以在内部使用dispatch_async,因此这可能会有问题.

在另一个线程中运行libevent的循环会使同步变得更加复杂,这是解决此延迟问题的唯一方法吗?

LINUX更新.上面的代码在Linux上不起作用(2016-07-25-a Swift snapshot),RunLoop.current().run存在错误.下面是一个工作的Linux版本,重新实现了计时器和dispatch_main.它遇到了相同的延迟问题:

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()
Run Code Online (Sandbox Code Playgroud)

ipm*_*mcc 4

在 GitHub 上快速搜索开源 Swift 基金会库就会发现,CFRunLoop不同平台上的支持(也许很明显)实现方式不同。从本质上讲,这意味着RunLooplibevent就跨平台而言,只是实现同一目标的不同方式。我可以看到这种想法背后的想法,这libevent可能更适合服务器实现,因为它CFRunLoop并不是带着这个特定目标成长的,但就跨平台而言,它们都在同一棵树上吠叫。

也就是说,RunLoop和使用的底层同步原语本质libevent上是私有的实现细节也许更重要的是,平台之间是不同的。从源代码来看,它看起来像在 Linux 上RunLoop使用,但在 macOS/iOS/etc 上,将使用 Mach 端口作为其基本原语,但看起来它将使用. 经过足够的努力,您可能能够制作一个与给定平台的源相关的混合体,但这可能非常脆弱,并且通常不明智,原因如下:首先,它将基于其私有实现细节不属于公共 API 的一部分,因此可能随时更改,恕不另行通知。其次,假设您没有对 Swift 和 都支持的每个平台都执行此操作,那么您就会破坏它的跨平台性,这也是您首先声明的原因之一。epolllibeventRunLooplibeventkqueueRunLoopSourcelibeventRunLooplibeventlibevent

您可能没有考虑过的另一种选择是单独使用 GCD,而不使用RunLoops. 查看 的文档dispatch_main。在服务器应用程序中,“主线程”(通常)没有什么特别的,因此分派到“主队列”应该足够好(如果需要的话)。您可以使用调度“源”来管理您的连接等。我个人无法谈论调度源如何扩展到 C10K/C100K/等。级别,但根据我的经验,它们看起来相当轻量级且开销低。我还怀疑像这样使用 GCD 可能是用Swift 编写服务器应用程序的最惯用的方法。我写了一个基于 GCD 的 TCP 回显服务器的小示例,作为此处另一个答案的一部分。

如果您必须并决定在同一个应用程序中使用两者RunLooplibevent那么正如您所猜测的那样,最好为其提供libevent自己的单独线程,但我认为它并不像您想象的那么复杂。您应该能够自由地dispatch_async进行libevent回调,并且类似地相当轻松地从 GCD 托管线程编组回复以libevent使用 的libevent多线程机制(即通过锁定运行,或将您的调用编组为libevent事件本身。)类似地,第三即使您选择使用 libevent 的循环结构,使用 GCD 的聚会库也不应该成为问题。GCD 管理自己的线程池,并且无法执行 的libevent主循环等。

您还可以考虑构建应用程序,以便使用什么并发和连接处理库并不重要。然后,您可以根据给定情况或部署最有效的方式更换libevent、GCD、CFStreams 等(或混合搭配)。选择并发方法很重要,但理想情况下,您不会将自己与它结合得太紧密,以至于在情况需要时无法切换。

当您拥有这样的架构时,我通常喜欢使用最高级别的抽象来完成工作,并且仅在特定情况需要时才降低到较低级别的抽象。在这种情况下,这可能意味着使用CFStreamsRunLoops来启动,并切换到“裸”GCD 或libevent更高版本,如果您碰壁并且还确定(通过经验测量)它是传输层而不是应用程序层。限制因素。很少有重要的应用程序真正解决传输层中的 C10K 问题;事情往往必须首先在应用程序层“扩展”,至少对于比基本消息传递更复杂的应用程序来说是这样。