在线程之间提升ASIO和消息传递

时间:2016-05-24 16:57:06

标签: c++ multithreading sockets boost

我正在设计一个 websocket 服务器,它接收一条消息并将其保存到嵌入式数据库。阅读我正在使用的邮件boost asio。要将消息保存到嵌入式数据库,我在前面看到了一些选项:

  1. 一旦我通过同一个帖子收到邮件,就会同步保存邮件。
  2. 在单独的线程上异步保存消息。
  3. 我很确定第二个答案就是我想要的。但是,我不知道如何将消息从套接字线程传递到IO线程。我看到以下选项:

    1. 每个线程使用一个io服务,并使用post function在线程之间进行通信。在这里,我不得不担心锁争用。我应该吗?
    2. 使用Linux域套接字在线程之间传递消息。据我所知,没有锁定争用。在这里,我可以使用BOOST_ASIO_DISABLE_THREADS宏来获得一些性能提升。
    3. 此外,我相信有多个IO线程会以循环方式接收消息以保存到嵌入式数据库。

      哪种架构效果最好?我提到的架构还有其他选择吗?

      有几点需要注意:

      • 消息的长度正好是8字节
      • 无法使用外部数据库。数据库必须嵌入在运行中 过程
      • 我正在考虑使用RocksDB作为嵌入式 数据库中。

5 个答案:

答案 0 :(得分:2)

我认为您不想使用unix套接字,它总是需要系统调用并通过内核传递数据。这通常比作为线程间机制更适合作为进程间机制。

除非您的数据库API要求所有调用都来自同一个线程(我怀疑),否则您不必使用单独的boost::asio::io_service。我会在现有io_service实例上创建io_service::strand,并使用strand::dispatch()成员函数(而不是io_service::post())来执行任何阻塞数据库任务。以这种方式使用strand可以保证最多可以阻止一个线程访问数据库,使io_service实例中的所有其他线程可用于服务非数据库任务。

为什么这可能比使用单独的io_service实例更好?一个优点是具有一组线程的单个实例的编码和维护稍微简单一些。另一个小优点是如果可以的话,使用strand::dispatch()将在当前线程中执行(即如果strand中没有任何任务已经运行),这可以避免上下文切换。

对于最终的优化,我同意使用一个排队操作无法进行系统调用的专用队列可能是最快的。但考虑到消费者有生产者和磁盘i / o的网络i / o,我不知道队列的实现将如何成为你的瓶颈。

答案 1 :(得分:1)

在基准测试/分析之后,我发现facebook {* 3}}的愚蠢执行速度至少是50%的最快。如果我使用MPMC Queue,那么套接字线程几乎没有开销,IO线程仍然忙。系统调用的数量也远远少于其他队列实现。

在boost中使用cond变量的SPSC队列较慢。我不知道为什么会这样。它可能与愚蠢队列使用的自适应旋转有关。

此外,消息传递(在这种情况下为UDP域套接字)结果显示速度要慢一些,特别是对于较大的消息。这可能与复制数据两次有关。

答案 2 :(得分:0)

您可能只需要一个io_service - 您可以创建其他线程,通过提供io_service作为线程函数来处理boost::asio::io_service::run内发生的事件。这应该适合通过网络套接字从客户端接收8字节消息。

为了将消息存储在数据库中,它取决于数据库和数据库。接口。如果它是多线程的,那么您也可以将每条消息从接收它的线程发送到DB。否则,我可能会设置一个boost::lockfree::queue,其中一个读取器线程将项目关闭并将它们发送到数据库,io_service线程在到达时将新消息附加到队列中。 / p>

这是最有效的方法吗?我不知道。它非常简单,并为您提供了一个基线,如果它不够快,您可以对其进行分析。但我建议一开始不要设计更复杂的东西:你根本不知道你是否需要它,除非你知道关于你的系统的很多,它就是&#39几乎不可能说复杂的方法是否会比简单的方法表现更好。

答案 3 :(得分:0)

void Consumer( lockfree::queue<uint64_t> &message_queue ) {
    // Connect to database...
    while (!Finished) {
        message_queue.consume_all( add_to_database ); // add_to_database is a Functor that takes a message
        cond_var.wait_for( ... ); // Use a timed wait to avoid missing a signal.  It's OK to consume_all() even if there's nothing in the queue.
    }
}

void Producer( lockfree::queue<uint64_t> &message_queue ) {
    while (!Finished) {
        uint64_t m = receive_from_network( );
        message_queue.push( m );
        cond_var.notify_all( );
    }
}

答案 4 :(得分:0)

假设在你的场景中使用cxx11的约束并不太难,我会尝试使用std::async对嵌入式数据库进行异步调用。