【问题标题】:Asynchronous threads race condition异步线程竞争条件
【发布时间】:2021-06-04 19:30:17
【问题描述】:

我被要求用两个异步线程来运行一个任务。一个应该移动车辆,另一个计算并绘制车辆覆盖的区域。代码的简化如下:

#include <iostream> 
#include <vector>
#include <math.h> 
#include <chrono>

using namespace std; 


class Robot {

    private:
        bool moving;
        vector<double> position;
        double area_covered;

    public:
        Robot(const vector<double> &initial_position) {
            moving = true;
            position = initial_position;
            area_covered = 0.0;
        }


        void get_area() {
            static vector<double> previous_measure = this->position; // initialized with the first position the robot is in

            while (this->moving) {
                this->area_covered += sqrt(pow(this->position[0] - previous_measure[0]) + pow(this->position[1] - previous_measure[1]));
                previous_measure = this->position; // save the new position for the next iteration
                this_thread::sleep_for(chrono::milliseconds(600)); // sleep for 600 ms
            }
        }


        void move_robot(const vector<vector<double> > &map) {
            for (int i=1; i < map.size(); i++) {
                this->position = map[i];
                this_thread::sleep_for(chrono::milliseconds(500)); // sleep for 500 ms
            }
            this->moving = false;
        }
};
  


int main () {
    vector<vector<double> > path{
      {0.0359, -0.013}, {0.0658, -0.0287},  {0.0736, -0.027}};
    
    Robot r3(path[0]);
    auto thread1 = std::async(&Robot::move_robot, &r3, path);
    auto thread2 = std::async(&Robot::get_area, &r3);
    thread1.join();
    thread2.join();
    return 0;
}

在方法get_area() 中,我多次使用this.position,这可能会有所不同,因为它在另一个线程中已更改。在执行get_area 时我不能阻塞另一个线程,但我必须避免在一个循环运行中使用不同的this.position。最简单的解决方案是创建另一个变量来保存this.position 的初始值,但是我想知道您是否有更好的 C++ 方法来做到这一点。它会是这样的:

        void get_area() {
            static vector<double> previous_measure = this->position; // initialized with the first position the robot is in
            vector<double> auxiliar;

            while (this->moving) {
                auxiliar = this->position;
                this->area_covered += sqrt(pow(auxiliar[0] - previous_measure[0]) + pow(auxiliar[1] - previous_measure[1]));
                previous_measure = auxiliar; // save the new position for the next iteration
                this_thread::sleep_for(chrono::milliseconds(600)); // sleep for 600 ms
            }
        }

此外,我需要在move_robot() 的方法/线程完成时通知get_area(),以便在下一次while 迭代中它也退出。现在我正在使用属性moving,但这样做我在每次迭代之前检查条件,而不是在最后。我可以在最后添加一个if 来检查它,但应该有一些更好的方法。

最后,我也非常感谢您对如何干净地将对象传递给两个异步线程并等待它们解决 C++ 方式的意见。

【问题讨论】:

  • 在您的示例中没有理由使用多线程。
  • 但是,您也可以将positions 推送到队列并从另一个线程处理它。
  • 我必须这样做。这是一个练习

标签: c++ multithreading


【解决方案1】:

这是非常不安全的代码,充满了 UB 和语法错误。

  • 您不能在不同步的情况下从一个线程写入内存并从另一个线程读取它。
  • 声明timespan 变量不会使线程进入睡眠状态。
  • 如果在move_robot 迭代时根本没有调用get_area 会发生什么?不太可能,但也不太可能获得 1:1 映射。
  • 睡眠不是同步原语。
  • this. 总是错误的语法。
  • std::async 不返回线程,而是返回 std::future

对 C++ 中的线程进行一些研究 - cppreference 也可以作为一个很好的教程及其示例。

您应该用std::atomic&lt;T&gt;this-&gt;moving 等基本类型重写代码,并为其他类型加锁。当然,只有当它们是从多个线程访问并且不是自然线程安全的时。

因为您需要 1:1 映射,所以使用 1 个生产者、1 个消费者队列和 std::condition_variable

  • move_robot 会推送到队列并通知消费者。
  • get_area 将等待通知,处理入队的元素并重新进入睡眠状态。
  • 您可以将毒丸添加到队列中,以向get_area 发出工作完成的信号。
  • 在 main 中生成两个线程并等待它们。

【讨论】:

  • 感谢@Quimby,我已经编辑了this. 和睡眠。如果两个线程以不同的频率刷新,您的方法是否也有效?想象一下,我希望每秒调用一次区域,但机器人每 0.1 秒移动一次
  • @HectorEsteban 你为什么要这样做?那么该区域将不正确。忘记这一点,您不能使用sleep 或任何其他原语来提供线程频率。 “每隔 X 秒调用一次”需要实时内核,你最喜欢不使用或拥有的东西。相反,您应该围绕同步点而不是时间来设计程序。如果您需要更具体的帮助,请准确说明您的要求。
  • 我使用 sleeps 是因为我想模拟机器人的运动并使程序不会立即完成,以便查看结果如何进行。我添加了this_thread::sleep_for(chrono::milliseconds(600));,我认为这是正确的方法。最后一个问题@Quimby,您能否通过使用带有属性移动和锁定的 std:atomic 添加一个玩具示例?我从未见过声明为原子的对象属性。非常感谢您的帮助。
  • 此外。使用您的方法,get_area 可以在计算面积时阻止方法move_robot 不是吗?我不想要那个
  • @HectorEsteban 请阅读有关线程的信息,这不是我可以在一个答案中总结的主题。你可以只添加std::atomic&lt;bool&gt; moving; 成员变量,不确定你想看到什么。不,只有当两者同时访问队列时,这些方法才会相互阻塞。所以get_area 只会在queue.pop() 期间阻塞,而不是整个调用。再次阅读线程概念和消费者-生产者队列。另外请注意move_robot 无论如何都会被调度程序任意中断和阻止。使用睡眠作为延迟是可以的。
猜你喜欢
  • 2010-11-27
  • 1970-01-01
  • 1970-01-01
  • 2018-04-17
  • 2020-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-12
相关资源
最近更新 更多