并行编写器和std :: vector的读者

时间:2013-03-17 11:47:00

标签: c++ multithreading concurrency vector c++11

我有一个同时由2个线程使用的类:一个线程将结果(逐个)添加到任务的results,第二个线程处理那些results那个已经在那里了。

// all members are copy-able
struct task {
    command cmd;
    vector<result> results;
};

class generator {
    public:
        generator(executor* e); // store the ptr
        void run();
        ...
};

class executor {
    public:
        void run();
        void add_result(int command_id, result r);
        task& find_task(int command_id);
            ...
    private:
        vector<task> tasks_;
        condition_variable_any update_condition_;
};

启动

// In main, we have instances of generator and executor,
// we launch 2 threads and wait for them.
std::thread gen_th( std::bind( &generator::run, gen_instance_) );
std::thread exe_th( std::bind( &executor::run,  exe_instance_) );

生成器线程

void generator::run() {
    while(is_running) {
        sleep_for_random_seconds(); 
        executor_->add_result( SOME_ID, new_result() );
    }
}

执行人员线程

void executor::add_result( int command_id, result r ) {
    std::unique_lock<std::recursive_mutex> l(mutex_);
    task& t = this->find_task(command_id);
    t.results.push_back(r);
    update_condition_.notify_all();
}

void executor::run() { 
  while(is_running) {
     update_condition_.wait(...);
     task& t = this->find_task(SOME_ID);        
     for(result r: t.results) {
        // no live updates are visible here
     }
   }
}
  1. 生成器线程每隔几秒就会添加一个结果。
  2. 执行者线程本身就是executor。它通过run方法运行,该方法等待更新,当发生这种情况时,它会对结果起作用。
  3. 很少有事情需要注意:

    1. 任务向量可能很大;结果永远不会被处理;
    2. 执行程序中的for-each循环获取它正在处理的任务,然后迭代结果,检查哪些是新的并处理它们。处理完成后,它们会被标记,不会再次处理。此处理可能需要一些时间。
    3. 执行程序线程在添加另一个结果之前没有完成for循环时,会出现问题 - 结果对象在for循环中不可见。由于执行程序线程正在运行,它不会注意到更新条件更新,不刷新向量等。完成后(处理 alread-not-actual视图 tasks_)它再次挂起update_condition_ ..刚被触发。

      我需要让代码知道,它应该在完成后再次运行循环for-each循环中可见的任务进行更改。这个问题的最佳解决方案是什么?

2 个答案:

答案 0 :(得分:1)

您只需要在阻止CV之前检查您的矢量是否为空。这样的事情:

while (running) {
    std::unique_lock<std::mutex> lock(mutex);
    while (tasks_.empty()) // <-- this is important
        update_condition_.wait(lock);
    // handle tasks_
}

如果您的体系结构允许它(即,如果您在处理任务时不需要保持锁定),您可能还需要在处理任务之前尽快解锁互斥锁,以便生产者可以在不执行任务的情况下执行更多任务阻塞。也许用临时的tasks_向量交换,然后解锁互斥锁,然后才开始处理临时向量中的任务:

while (running) {
    std::unique_lock<std::mutex> lock(mutex);
    while (tasks_.empty())
        update_condition_.wait(lock);
    std::vector<task> localTasks;
    localTasks.swap(tasks_);
    lock.unlock(); // <-- release the lock early
    // handle localTasks
}

编辑啊现在我意识到这并不适合您的情况,因为您的邮件不是直接在tasks_中,而是在tasks_.results中。虽然你得到了我的一般想法,但使用它将需要在代码中更改结构(例如,展平你的任务/结果,并且总是有一个与单个结果相关联的cmd)。

答案 1 :(得分:0)

我在同样的情况下以下列方式行事

std::vector< ... > temp;
mutex.lock();
temp.swap( results );
mutex.unlock();
for(result r: temp ){
    ...
}

一点开销就占了一席之地,但一般来说整个代码更具可重复性,如果计算量很大,那么复制的时间就会变为零(对不起英语 - 对我来说不是原生的)))