【问题标题】:Which threading model can I choose?我可以选择哪种线程模型?
【发布时间】:2013-08-13 07:23:59
【问题描述】:

我正在编写一个类似于键值存储的系统,但不同的是,get 和 set 操作经常被调用。

在服务器端,我刚才使用了“每个客户端一个线程”的线程模型。但是在这个应用程序中,每个任务的客户端连接数大约是一万,所以我使用的模型很慢。

在这种情况下我可以选择哪种线程模型?

谢谢

【问题讨论】:

  • 您正在使用哪种编程语言?

标签: multithreading redis pthreads memcached threadpool


【解决方案1】:

实际上,“每个客户端一个线程”模型非常有效。在原始性能、延迟、系统调用数量方面,很难做得更好。但是,它的可扩展性不是很好,因为:

  • 每个线程都有自己的栈,所以内存/连接比不好
  • 操作系统调度开销随着线程数的增加而增加
  • 当大量线程在同一资源上阻塞时,并发管理原语(互斥体、条件变量等)效率较低。

我认为选择一个好的线程模型的关键是评估你的操作的相对成本,以及它们是否可以阻塞。例如,协议编组/解组的成本是否高于访问内部数据结构的成本?数据访问能否生成阻塞磁盘 I/O?等等……

根据结果,您可以想象各种模型。

第一个可能的模型(memcached 模型):

  • 1 个线程(事件循环)用于信号管理和 TCP 接受管理。 每次接受新连接时,都会将其分派(通过循环)到其中一个连接管理线程

  • n 个线程用于连接管理。 每个线程都是对其关联连接的偶数循环(e)轮询。任何传入的查询都由线程处理。对全局数据结构的任何访问都必须受到某种互斥机制的保护。

如果数据结构访问是快速且可预测的操作,则此模型可以正常工作。如果互斥过于复杂,或者全局数据结构争用过多,可以通过增加专用线程来管理数据结构操作来改进该模型。

  • 1 个线程(事件循环)用于信号管理和 TCP 接受管理。
  • n 个线程(事件循环)用于连接管理
  • 快速内存队列
  • 1 个线程处理所有数据结构访问,监听内存中的快速队列

在此模型中,连接线程对协议进行解码/编码(昂贵的操作),并通过将事件发布到内存队列来将所有数据访问委托给专用线程。一旦专用线程为连接线程发布结果,就会使用特定的文件描述符来唤醒事件循环。所有数据访问操作都是序列化的,不需要互斥体来保护相关的数据结构。

以前的模型假设数据访问比协议管理本身便宜或便宜。如果这是错误的,那么下面的模型可能会更好:

  • 1 个线程(事件循环)用于信号管理和 TCP 接受管理。
  • n 个线程(事件循环)用于连接管理
  • 快速内存队列
  • m 线程用于数据/查询管理,监听内存队列

在此模型中,n 个连接线程仅处理协议编码/解码操作,并将其他所有内容委托给 m 个线程池。这些线程必须管理对全局数据结构的并发访问。最终,只要你有足够的 I/O 或繁重的计算,它们就会阻塞。

还有许多其他可能的变化,这些只是主要想法。

【讨论】:

  • Didier 出色答案的唯一详细说明是您可以使事件循环线程自缩放。如果使用 epoll() 的事件循环线程分析它在 epoll() 中阻塞的平均时间,它可以确定它有多忙。如果 epoll() 总是立即返回,那么线程跟不上需求。您所需要的只是该线程能够将其一些连接传递回管理线程的方法。这反过来又启动另一个线程来处理这些连接。听起来需要大量编码,但您最终会得到一个自我调整的服务器应用程序。
【解决方案2】:

您可能应该提供其他详细信息,说明您看到系统为何如此缓慢。如果您使用互斥锁,您是如何设计它们的?互斥体是否保护整个键值存储或其子集。通常,将大表拆分为单独的小表(行)有助于使事情变得更加并行。此外,您是否遇到了线程等待另一个线程的问题。如果是这样,您可能最好使用条件变量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多