Cocoa中的并发网络客户端

问题描述:

我正试图在我的脑海中构建一个基本上是并发下载管理器的Cocoa应用程序的最佳方式。应用程序会与之对话的一个服务器,用户列出一大堆要下拉的内容,然后应用程序处理该列表。 (它不使用HTTP或FTP,所以我不能使用URL加载系统;我将通过套接字连接进行通信。)Cocoa中的并发网络客户端

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

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

看来我可以使用NSOperationQueue,并调用setMaxConcurrentOperationCount:与连接数。然后我只是把下载请求扔进那个队列。但在这种情况下,我不确定如何自己管理连接。 (只要把它们放在一个堆栈上,并且依靠队列来确保我不会超出/低于运行?在dispatch semaphore连同堆栈一起投掷?)

现在我们处于勇敢的新世界Grand Central Dispatch,这是否打开了解决这个问题的其他方法?乍一看,它看起来并不像它,因为GCD的动态扩展并发性的能力(并在苹果的Changing Producer-Consumer Implementations建议中提到)实际上并不能帮助我。但我只是抓了一下阅读的表面。

编辑:

在如此重要的情况:是的,我打算使用异步/非阻塞套接字的API做的与服务器的实际通信。所以I/O本身不一定要在它自己的线程上。我只关心排队工作的机制,并且(安全地)将它放到连接处,因为它们变得可用。

为后人的缘故,后在其他地方的一些讨论,我想我会采用这种解决方案基本上是:

  • 具有挂起下载操作的队列,初始为空。
  • 有一组包含所有打开的连接,最初为空。
  • 有一个可变数组(空闲,打开连接,最初为空)。
  • 当用户增加了一个下载请求:
    • 如果空闲连接的阵列是不为空,删除一个和下载分配给它。
    • 如果没有空闲连接,但总连接数尚未达到其限制,请打开一个新连接,将其添加到该集中,并为其分配下载。
    • 否则,请稍后排队下载。
  • 当下载完成时:如果有排队的请求,则将一个 退出并将其提供给连接;否则,将连接添加到空闲列表。

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

打开一个新的连接可能需要一段时间,因此,创建一个新的可能是一点点在实际应用中更复杂(比如过程,排队下载,发起连接过程,然后出队它当连接完全建立)。但我仍然认为我对竞争条件可能性的看法被夸大了。

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

除此之外,它听起来像你的应用程序需要做的唯一的其他工作是保持一个项目队列下载。当任何一个传输完成时,CFSocket回调可以启动传输队列中的下一个项目。 (如果队列为空,则减少连接数,并且如果将某些内容添加到空队列中,则启动新的传输。)我不明白为什么需要多个线程。可能你遗漏了一些重要的东西,但根据你的描述,这个应用程序是I/O绑定的,而不是CPU绑定的,所以所有并发的东西都会使代码变得更复杂,对性能影响最小。

在主线程中执行所有操作。

+1

该应用程序几乎肯定不受CPU限制。下载完成后会有一些解码任务,但我可以将它们发送给GCD进行处理。我主要关注的问题是1)保持UI的响应,2)避免任何竞争条件下工作。如果I/O回调函数全部在主线程上运行,这通常可以解决(2),但是我想知道它是以(1)为代价的。据推测,大部分工作只是将东西添加到缓冲区中,而且应该很快。 – 2009-09-30 00:05:48