使用boost :: thread的Actor计算模型

时间:2013-10-25 15:06:52

标签: c++ multithreading boost actor

我正在尝试使用boost :: thread在C ++上的线程上实现Actor计算模型。 但程序在执行期间抛出奇怪的异常。例外情况不稳定,有时候程序工作正常。

我的代码:

actor.hpp

class Actor {

  public:
    typedef boost::function<int()> Job;

  private:
    std::queue<Job>             d_jobQueue;
    boost::mutex                d_jobQueueMutex;
    boost::condition_variable   d_hasJob;
    boost::atomic<bool>         d_keepWorkerRunning;
    boost::thread               d_worker;

    void workerThread();

  public:
    Actor();
    virtual ~Actor();

    void execJobAsync(const Job& job);

    int execJobSync(const Job& job);
};

actor.cpp

namespace {

int executeJobSync(std::string          *error,
                   boost::promise<int> *promise,
                   const Actor::Job     *job)
{
    int rc = (*job)();

    promise->set_value(rc);
    return 0;
}

}

void Actor::workerThread()
{
    while (d_keepWorkerRunning) try {
        Job job;
        {
            boost::unique_lock<boost::mutex> g(d_jobQueueMutex);

            while (d_jobQueue.empty()) {
                d_hasJob.wait(g);
            }

            job = d_jobQueue.front();
            d_jobQueue.pop();
        }

        job();
    }
    catch (...) {
        // Log error
    }
}

void Actor::execJobAsync(const Job& job)
{
    boost::mutex::scoped_lock g(d_jobQueueMutex);
    d_jobQueue.push(job);
    d_hasJob.notify_one();
}

int Actor::execJobSync(const Job& job)
{
    std::string error;
    boost::promise<int> promise;
    boost::unique_future<int> future = promise.get_future();

    {
        boost::mutex::scoped_lock g(d_jobQueueMutex);
        d_jobQueue.push(boost::bind(executeJobSync, &error, &promise, &job));
        d_hasJob.notify_one();
    }

    int rc = future.get();

    if (rc) {
        ErrorUtil::setLastError(rc, error.c_str());
    }

    return rc;
}

Actor::Actor()
: d_keepWorkerRunning(true)
, d_worker(&Actor::workerThread, this)
{
}

Actor::~Actor()
{
    d_keepWorkerRunning = false;
    {
        boost::mutex::scoped_lock g(d_jobQueueMutex);
        d_hasJob.notify_one();
    }
    d_worker.join();
}

实际上抛出的异常是int rc = future.get();行中的boost :: thread_interrupted。但形式提升文档我不能推理这个例外。 Docs说

  

抛出: - 如果与* this关联的结果在调用点没有准备好,并且当前线程被中断,则boost :: thread_interrupted。

但我的工作线程不能处于中断状态。

当我使用gdb并设置“catch throw”时,我看到后面的跟踪看起来像

  

抛出thread_interrupted

     

升压::详细:: interruption_checker :: check_for_interruption

     

升压::详细:: interruption_checker :: interruption_checker

     

升压:: condition_variable ::等待

     

升压::详细:: future_object_base :: wait_internal

     

升压::详细:: future_object_base ::等待

     

升压::详细:: future_object ::获得

     

的boost :: unique_future ::获得

我查看了boost源但无法理解为什么interruption_checker决定工作线程被中断。

所以有人C ++大师,请帮助我。我需要做些什么来获得正确的代码? 我正在使用:

提升1_53

Linux版本2.6.18-194.32.1.el5 Red Hat 4.1.2-48

gcc 4.7

  

修改

     

修正了它!感谢Evgeny Panasyuk和Lazin。问题出在TLS上   管理。 boost :: thread和boost :: thread_specific_ptr正在使用   相同的TLS存储用于其目的。在我的情况下有问题   他们都试图在创作时改变这个存储空间(不幸的是我   没有得到详细信息的原因)。所以TLS被破坏了。

     

我用__thread替换了我的代码中的boost :: thread_specific_ptr   指定变量。

     

Offtop:在调试过程中,我发现外部库中存在内存损坏   并修复它=)

  

编辑2   我遇到了确切的问题......这是GCC中的一个错误=)   _GLIBCXX_DEBUG编译标志打破了ABI。   你可以看看关于boost bugtracker的讨论:     https://svn.boost.org/trac/boost/ticket/7666

2 个答案:

答案 0 :(得分:5)

我发现了几个错误:


Actor::workerThread功能会在d_jobQueueMutex上进行双重解锁。首先解锁是手动d_jobQueueMutex.unlock();,第二个是boost::unique_lock<boost::mutex>的析构函数。

您应该阻止解锁之一,例如unique_lockmutex之间的release关联:

g.release(); // <------------ PATCH
d_jobQueueMutex.unlock();

或添加其他代码块+默认构造的Job


workerThread可能永远不会离开以下循环:

while (d_jobQueue.empty()) {
    d_hasJob.wait(g);
}

想象一下以下情况:d_jobQueue为空,调用Actor::~Actor(),它设置标志并通知工作线程:

d_keepWorkerRunning = false;
d_hasJob.notify_one();

workerThread在while循环中唤醒,看到该队列为空并再次休眠。

通常的做法是发送特殊的最终作业来停止工作线程:

~Actor()
{
    execJobSync([this]()->int
    {
        d_keepWorkerRunning = false;
        return 0;
    });
    d_worker.join();
}

在这种情况下,d_keepWorkerRunning不需要是原子的。


LIVE DEMO on Coliru


修改

  

我已在您的示例中添加了事件队列代码。

EventQueueImplActor都有并发队列,但是对于不同的类型。可以将公共部分提取到适用于任何类型的单独实体concurrent_queue<T>中。在一个地方调试和测试队列比捕获分散在不同类上的错误要容易得多。

因此,您可以尝试使用此concurrent_queue<T>(on Coliru)

答案 1 :(得分:2)

这只是猜测。我认为有些代码实际上可以调用boost::tread::interrupt()。您可以将断点设置为此函数,并查看对此负责的代码。您可以在execJobSync中测试中断:

int Actor::execJobSync(const Job& job)
{
    if (boost::this_thread::interruption_requested())
        std::cout << "Interruption requested!" << std::endl;
    std::string error;
    boost::promise<int> promise;
    boost::unique_future<int> future = promise.get_future();

在这种情况下,最可疑的代码是一个引用线程对象的代码。

无论如何,最好让你的boost :: thread代码中断。某些范围也可以disable interruption

如果不是这种情况 - 您需要检查与线程本地存储一起使用的代码,因为线程中断标志存储在TLS中。也许你的一些代码会重写它。您可以在此类代码片段之前和之后检查中断。

另一种可能性是你的记忆力已经腐败。如果没有代码调用boost :: thread :: interrupt()并且您不能使用TLS。这是最难的情况,尝试使用一些动态分析器 - valgrind或clang内存消毒剂。

<强> Offtopic: 您可能需要使用一些并发队列。由于高内存争用,std :: queue会非常慢,最终会导致缓存性能下降。良好的并发队列允许您的代码并行排队和出列元素。

此外,演员不是应该执行任意代码的东西。 Actor队列必须接收简单的消息,而不是函数!你在写一个工作队列:)你需要看看像Akkalibcpa这样的演员系统。