在 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
中的支持在不同平台上(可能很明显)实现方式不同。这意味着,就跨平台性而言,本质上 RunLoop
和 libevent
只是实现同一件事的不同方式。我可以看到 libevent
可能更适合服务器实现的想法背后的想法,因为 CFRunLoop
并没有为那个特定目标而成长,但就跨平台而言,他们'我们都在同一棵树上狂吠。
也就是说,RunLoop
和 libevent
使用的底层同步原语本质上是私有实现细节 和 ,也许更重要的是,不同平台。从源头上看,RunLoop
在 Linux 上使用 epoll
,libevent
也是如此,但在 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.
的文档
如果您被约束并决定在同一个应用程序中同时使用 RunLoop
和 libevent
,正如您所猜测的那样,最好给 libevent
它自己单独的线程,但我认为它并不像您想象的那么复杂。您应该能够从 libevent
回调中自由地 dispatch_async
,并且类似地使用 libevent
的多线程机制相当容易地将来自 GCD 管理线程的回复编组到 libevent
(即通过 运行 锁定,或者通过将您的调用编组到 libevent
作为事件本身。)同样,即使您选择使用 libevent 的循环结构,使用 GCD 的第三方库也不应该成为问题。 GCD 管理自己的线程池,无法进入 libevent
的主循环等
您还可以考虑构建您的应用程序,以便使用什么并发和连接处理库都无关紧要。然后,您可以换出 libevent
、GCD、CFStreams 等(或混合搭配),具体取决于给定情况或部署的最佳效果。选择并发方法很重要,但理想情况下,您不会将自己与它紧密结合,以至于在情况需要时无法切换。
当您拥有这样的体系结构时,我通常喜欢使用最高级别的抽象来完成工作,并且仅在特定情况需要时才降低到较低级别的抽象的方法。在这种情况下,这可能意味着使用 CFStreams
和 RunLoops
开始,然后切换到 "bare" GCD 或 libevent
之后,如果你撞墙 and 还确定(通过经验测量)传输层而不是应用层是限制因素。很少有重要的应用程序真正解决传输层中的 C10K 问题;事情往往必须首先在应用程序层扩展 "out",至少对于比基本消息传递更复杂的应用程序而言。
我正在 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
中的支持在不同平台上(可能很明显)实现方式不同。这意味着,就跨平台性而言,本质上 RunLoop
和 libevent
只是实现同一件事的不同方式。我可以看到 libevent
可能更适合服务器实现的想法背后的想法,因为 CFRunLoop
并没有为那个特定目标而成长,但就跨平台而言,他们'我们都在同一棵树上狂吠。
也就是说,RunLoop
和 libevent
使用的底层同步原语本质上是私有实现细节 和 ,也许更重要的是,不同平台。从源头上看,RunLoop
在 Linux 上使用 epoll
,libevent
也是如此,但在 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.
如果您被约束并决定在同一个应用程序中同时使用 RunLoop
和 libevent
,正如您所猜测的那样,最好给 libevent
它自己单独的线程,但我认为它并不像您想象的那么复杂。您应该能够从 libevent
回调中自由地 dispatch_async
,并且类似地使用 libevent
的多线程机制相当容易地将来自 GCD 管理线程的回复编组到 libevent
(即通过 运行 锁定,或者通过将您的调用编组到 libevent
作为事件本身。)同样,即使您选择使用 libevent 的循环结构,使用 GCD 的第三方库也不应该成为问题。 GCD 管理自己的线程池,无法进入 libevent
的主循环等
您还可以考虑构建您的应用程序,以便使用什么并发和连接处理库都无关紧要。然后,您可以换出 libevent
、GCD、CFStreams 等(或混合搭配),具体取决于给定情况或部署的最佳效果。选择并发方法很重要,但理想情况下,您不会将自己与它紧密结合,以至于在情况需要时无法切换。
当您拥有这样的体系结构时,我通常喜欢使用最高级别的抽象来完成工作,并且仅在特定情况需要时才降低到较低级别的抽象的方法。在这种情况下,这可能意味着使用 CFStreams
和 RunLoops
开始,然后切换到 "bare" GCD 或 libevent
之后,如果你撞墙 and 还确定(通过经验测量)传输层而不是应用层是限制因素。很少有重要的应用程序真正解决传输层中的 C10K 问题;事情往往必须首先在应用程序层扩展 "out",至少对于比基本消息传递更复杂的应用程序而言。