在 Swift 中将 libevent 与 GCD (libdispatch) 一起使用

Using libevent together with GCD (libdispatch) in Swift

我正在 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 处于休眠状态时,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 上的开源 Swift 基金会库表明 CFRunLoop 中的支持在不同平台上(可能很明显)实现方式不同。这意味着,就跨平台性而言,本质上 RunLooplibevent 只是实现同一件事的不同方式。我可以看到 libevent 可能更适合服务器实现的想法背后的想法,因为 CFRunLoop 并没有为那个特定目标而成长,但就跨平台而言,他们'我们都在同一棵树上狂吠。

也就是说,RunLooplibevent 使用的底层同步原语本质上是私有实现细节 ,也许更重要的是,不同平台。从源头上看,RunLoop 在 Linux 上使用 epolllibevent 也是如此,但在 macOS/iOS/etc 上,RunLoop 将使用Mach 端口作为其基本原语,但 libevent 看起来它将使用 kqueue。通过足够的努力,您可能能够制作一个混合 RunLoopSource 与给定平台的 libevent 源相关联,但这可能非常脆弱,并且通常不适合一对夫妇原因:首先,它将基于不属于 public API 的 RunLoop 的私有实现细节,因此随时可能更改,恕不另行通知。其次,假设您没有对 Swift 和 libevent 支持的每个平台都执行此操作,您就会破坏它的跨平台性,这是您陈述的原因之一首先选择 libevent

您可能没有考虑过的另一个选项是单独使用 GCD,而不使用 RunLoops。查看 dispatch_main. In a server application, there's (typically) nothing special about a "main thread," so dispatching to the "main queue", should be good enough (if needed at all). You can use dispatch "sources" to manage your connections, etc. I can't personally speak to how dispatch sources scale up to the C10K/C100K/etc. level, but they've seemed pretty lightweight and low-overhead in my experience. I also suspect that using GCD like this would likely be the most idiomatic way to write a server application in Swift. I've written up a small example of a GCD-based TCP echo server as part of another answer here.

的文档

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

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

当您拥有这样的体系结构时,我通常喜欢使用最高级别的抽象来完成工作,并且仅在特定情况需要时才降低到较低级别的抽象的方法。在这种情况下,这可能意味着使用 CFStreamsRunLoops 开始,然后切换到 "bare" GCD 或 libevent 之后,如果你撞墙 and 还确定(通过经验测量)传输层而不是应用层是限制因素。很少有重要的应用程序真正解决传输层中的 C10K 问题;事情往往必须首先在应用程序层扩展 "out",至少对于比基本消息传递更复杂的应用程序而言。