如何在等待时保持消息传递?

时间:2009-06-20 23:38:49

标签: c++ message-queue wait threadpool

我有一个基于消息泵线程池结构的应用程序。只要存在可以阻止的动作,它就会被实现为“完成/触发evnet回调”动作,因此它不会使正在执行的线程停止。

虽然这种技术适用于大多数情况,但有些情况会变得非常不方便并使代码过于复杂。

我希望能够以透明的方式在等待时继续处理事件,而不会将功能分解为等待前/后的部分。

我该怎么做?

我有两个选择:

  1. 在等待时从执行功能中运行消息循环。
  2. 在等待时创建一个新的工作线程,并在恢复时以适当的方式终止它。
  3. 两种选择都有其缺陷,仅举几例:

    1:

    • 可能导致堆栈溢出。
    • 可能最终死锁。
    • 如果内部消息导致等待第二个事件完成,并且外部事件同时完成,则外部函数无法继续,直到第二个事件完成,这种情况可能会扩展。

    选项2最终可以创建越来越多的线程。

    当然,可能还有其他我没有想到的选择。

    编辑:语言是C ++,因此无法以简单(可移植?)的方式逐步推卸出功能。平台是Windows(API),虽然我不认为它是相关的。

5 个答案:

答案 0 :(得分:2)

对于便携式C ++,这是行不通的,但既然你提到你的平台是Windows,为什么不使用MsgWaitForMultipleObjects?它的目的是让你完全按照你的问题所说的做法 - 在等待的同时保持消息。

答案 1 :(得分:0)

编辑:你提到不想“将功能分解为等待前/后的部分。”

你在用什么语言发展?如果它有连续性(C#中为yield return)那么它提供了一种编写看似程序的代码的方法,但是在阻塞操作完成回调之前可以很容易地暂停代码。

这是一篇关于这个想法的文章:http://msdn.microsoft.com/en-us/magazine/cc546608.aspx

<强>更新

  

不幸的是,语言是C ++

这将是一个伟大的T恤口号。

好的,您可能会发现将顺序代码构建为状态机很有帮助,因此它具有中断/恢复功能。

e.g。你的痛苦需要写两个函数,一个启动的函数和一个作为完成事件的处理函数的函数:

void send_greeting(const std::string &msg)
{
    std::cout << "Sending the greeting" << std::endl;
    begin_sending_string_somehow(msg, greeting_sent_okay);
}

void greeting_sent_okay()
{
    std::cout << "Greeting has been sent successfully." << std::endl;
}

你的想法是等待:

void send_greeting(const std::string &msg)
{
    std::cout << "Sending the greeting" << std::endl;

    waiter w;
    begin_sending_string_somehow(msg, w);
    w.wait_for_completion();

    std::cout << "Greeting has been sent successfully." << std::endl;
}

在该示例中,waiter重载operator(),因此它可以作为回调,wait_for_completion以某种方式挂起,直到它看到operator()被调用。

我假设begin_sending_string_somehow的第二个参数是一个模板参数,可以是任何不接受参数的可调用类型。

但正如你所说,这有弊端。每当一个线程像这样等待时,你就添加了另一个潜在的死锁,你也消耗了整个线程及其堆栈的“资源”,这意味着必须在其他地方创建更多的线程才能完成工作,这与线程池的整个观点相矛盾。

相反,写一个类:

class send_greeting
{
    int state_;
    std::string msg_;

public:
    send_greeting(const std::string &msg)
        : state_(0), msg_(msg) {}

    void operator()
    {
        switch (state_++)
        {
            case 0:
                std::cout << "Sending the greeting" << std::endl;
                begin_sending_string_somehow(msg, *this);
                break;

            case 1:
                std::cout << "Greeting has been sent successfully." 
                          << std::endl;
                break;
        }
    }
};

该类实现函数调用操作符()。每次调用它时,它都会执行逻辑中的下一步。 (当然,这是一个微不足道的例子,现在主要是状态管理噪声,但在一个更复杂的例子中有四个或五个状态,它可能有助于澄清代码的顺序性质。)

<强>问题:

  • 如果事件回调函数签名具有特殊参数,则需要添加另一个operator()重载,该重载将参数存储在额外字段中,然后调用无参数重载。然后它开始变得混乱,因为这些字段在初始状态下可以在编译时访问,即使它们在运行时在该状态下没有意义。

  • 如何构建和删除类的对象?该对象必须存活直到操作完成或被放弃...... C ++的核心陷阱。我建议实施一般方案来管理它。创建一个“需要删除的东西”列表,并确保在某些安全点自动发生这种情况,即尽可能地尽可能接近GC。离你越远,你将会泄漏更多的记忆。

答案 2 :(得分:0)

在不了解您的具体应用程序的情况下(即消息需要多长时间处理等等),将会有很多手段:

  • 这是托管或非托管C ++吗?

  • 您使用的是哪个ThreadPool?

    • QueueUserWorkItem?
    • 您自己的游泳池是否通过CreateIoCompletionPort?
    • 或Vista的SubmitThreadpoolWork?

我认为平台有点相关,因为线程池的本质很重要。

例如:

如果您对线程池使用(Completion Ports)(即CreateIoCompletionPort)。您可以控制并发运行的线程数(因此最终创建总线程数)。如果将最大并发线程数设置为4. Windows将尝试仅允许4个线程同时运行。如果所有4个线程都忙于处理并且您排队第5个项目,则窗口将不允许该项目运行,直到4个中的一个完成(重新使用该线程)。该规则被破坏的唯一时间是线程被阻塞(即等待I / O),然后允许更多线程运行。

了解完成端口以及平台相关的原因非常重要。在不涉及内核的情况下实现这样的事情是非常困难的。了解忙线程和被阻塞线程之间的区别需要访问线程状态。完成端口在进入内核的上下文切换次数方面也非常有效。

回到你的问题:

看起来您应该有一个线程来处理/分发消息,并且通过将工作者推送到线程池来处理消息处理。让Completion端口处理负载平衡和并发。消息处理循环永远不会阻塞,可以继续处理消息。

如果收到的邮件的速度远远超过了你处理它们的能力,那么你可能不得不注意你的队列大小并在它变得太大时阻塞。

答案 3 :(得分:0)

看来你的问题是根本的,与C ++无关。其他语言可能更好地隐藏堆栈使用,但只要你没有从Foo()返回,你需要Foo()的调用堆栈。如果你还在执行Bar(),那也需要一个callstack。

线程是一种很好的方法,因为每个线程都有自己的callstack。 Continuations是一种智能但复杂的方法来保存callstacks,所以在可用的地方也可以选择。但是,如果你不想要那些,你将不得不使用一个callstack。

使用一个callstack进行Daling需要解决重入问题。在这里,关于什么是可能的,没有通用的答案。通常,您将拥有一组消息M1..Mx,它们由函数F1 ... Fy处理,具有一些特定于应用程序且可能依赖于状态的映射。使用可重入的消息循环,您可能在收到Mj时执行Fi。现在问题是该做什么。并非所有函数F1 ... Fn都可以调用;特别是Fi本身可能无法调用。然而,一些其他功能也可能是不可用的,例如,因为他们共享资源。这取决于应用程序。

如果Mj的处理需要任何这些不可用的功能,你必须推迟它。你能接受队列中的下一条消息吗?同样,这依赖于实现,甚至可能与消息类型和内容有关。如果消息足够独立,则可以不按顺序执行它们。这很快变得相当复杂 - 要确定是否可以接受队列中的第N个消息,您必须检查它是否可以相对于前面的N-1个消息无序执行。

语言可以通过不隐藏依赖关系来帮助您,但最终您必须做出明确的决定。没有银弹。

答案 4 :(得分:0)

您的问题是正确同步线程吗?如果这是你的问题,为什么不使用互斥?它可以用一个界面包裹起来。实际上,您可以使用PIMPL惯用法使互斥锁可移植。

http://msdn.microsoft.com/en-us/library/system.threading.mutex(VS.71).aspx