4

我正在尝试在脑海中找出构建 Cocoa 应用程序的最佳方式,该应用程序本质上是一个并发下载管理器。有一个应用程序与之对话的服务器,用户列出了很多要下拉的东西,然后应用程序处理该列表。(它没有使用 HTTP 或 FTP,所以我不能使用 URL 加载系统;我将讨论套接字连接。)

这基本上是经典的生产者-消费者模式。诀窍是消费者的数量是固定的,而且他们是持久的。服务器对可以同时打开的连接数(尽管通常至少两个)设置了严格的限制,并且打开新连接的成本很高,因此在理想情况下,相同的 N 个连接在应用程序的整个生命周期内都是打开的。

解决此问题的一种方法可能是创建 N 个线程,每个线程都将“拥有”一个连接,并在请求队列上等待,如果它为空则阻塞。由于连接的数量永远不会很大,这在实际系统开销方面并非不合理。但从概念上讲,Cocoa 似乎必须提供更优雅的解决方案。

似乎我可以使用, 并使用连接数进行NSOperationQueue调用。setMaxConcurrentOperationCount:然后我只是将下载请求扔到那个队列中。但在这种情况下,我不确定如何自己管理连接。(只需将它们放在堆栈上,并依靠队列来确保我不会过度运行/运行不足?与堆栈一起投入调度信号量?)

现在我们处于Grand Central Dispatch的美丽新世界,这是否开辟了解决这个问题的任何其他方法?乍一看,它看起来不像,因为 GCD 动态扩展并发的旗舰能力(在 Apple 关于改变生产者-消费者实现的建议中提到)实际上并没有帮助我。但我只是触及了阅读它的皮毛。

编辑:

万一这很重要:是的,我计划使用异步/非阻塞套接字 API 与服务器进行实际通信。所以 I/O 本身不必在自己的线程上。我只关心排队工作的机制,并(安全地)将其分配给连接,因为它们变得可用。

4

2 回答 2

1

如果您使用 CFSocket 的 I/O 非阻塞调用,我同意,这一切都应该发生在主线程上,让操作系统处理并发问题,因为您只是在复制数据而不是真正进行任何计算。

除此之外,听起来您的应用程序需要做的唯一其他工作就是维护要下载的项目队列。当任何一个传输完成时,CFSocket 回调可以启动队列中下一个项目的传输。(如果队列为空,则减少连接计数,如果将某些内容添加到空队列中,则开始新的传输。)我不明白为什么需要多个线程。

也许您遗漏了一些重要的东西,但根据您的描述,该应用程序受 I/O 限制,而不是 CPU 限制,因此所有并发内容只会生成更复杂的代码,而对性能的影响最小。

在主线程上完成所有操作。

于 2009-09-25T16:58:49.420 回答
0

为了子孙后代,在其他地方进行了一些讨论之后,我认为我会为此采用的解决方案基本上是:

  • 有一个等待下载操作的队列,最初是空的。
  • 有一个包含所有打开连接的集合,最初是空的。
  • 有一个空闲打开连接的可变数组(实际上是队列),最初是空的。
  • 当用户添加下载请求时:
    • 如果空闲连接数组不为空,则删除一个并将下载分配给它。
    • 如果没有空闲连接,但总连接数未达到限制,则打开一个新连接,将其添加到集合中,并将下载分配给它。
    • 否则,将下载排入队列以备后用。
  • 下载完成时:如果有排队的请求,则出列一个并将其提供给连接;否则,将连接添加到空闲列表。

所有这些工作都将在主线程上进行。解码每次下载结果的工作将被卸载到 GCD,因此它可以处理并发限制,并且不会阻塞主线程。

打开一个新连接可能需要一段时间,因此在实际实践中创建一个新连接的过程可能会稍微复杂一些(例如,将下载入队,启动连接过程,然后在连接完全建立后将其出队)。但我仍然认为我对比赛条件可能性的看法被夸大了。

于 2010-08-24T18:38:45.250 回答