代码之家  ›  专栏  ›  技术社区  ›  Sixten Otto

Cocoa中的并发网络客户端

  •  4
  • Sixten Otto  · 技术社区  · 15 年前

    我正努力在头脑中找到构建Cocoa应用程序的最佳方法,它本质上是一个并发下载管理器。有一个服务器,应用程序与之对话,用户列出一个大的下拉列表,应用程序处理该列表。(它不使用HTTP或FTP,因此我不能使用URL加载系统;我将在套接字连接之间进行对话。)

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

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

    好像我可以用 NSOperationQueue ,然后呼叫 setMaxConcurrentOperationCount: 连接数。然后我将下载请求放入队列。但在这种情况下,我不确定如何管理连接本身。(只需将它们放在一个堆栈上,并依靠队列来确保我不会运行过度/不足?投掷一个 dispatch semaphore 和烟囱一起吗?)

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

    编辑:

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

    2 回复  |  直到 14 年前
        1
  •  1
  •   benzado    15 年前

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

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

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

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

        2
  •  0
  •   Sixten Otto    14 年前

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

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

    所有这些工作都将在主线程上进行。解码每个下载结果的工作将被卸载到gcd,这样它就可以处理限制并发性的问题,并且不会阻塞主线程。

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