[英]Avoid race condition performance
I have two asynchronous threads (explicitly asked to do it that way) running 2 different methods from an object, move_robot()
and get_area()
.我有两个异步线程(明确要求这样做)从 object、
move_robot()
和get_area()
) 运行 2 种不同的方法。 The first method moves a vehicle through different positions and the second one calculates the area covered thus far.第一种方法使车辆移动通过不同的位置,第二种方法计算到目前为止所覆盖的区域。 Both share two object attributes:
两者共享两个 object 属性:
vector<double> position; // saves current position of the vehicle
bool moving; // true if the vehicle has not reach the end of the path
In order to avoid race conditions I am using locks the following way:为了避免竞争条件,我使用以下方式锁定:
void get_area() {
std::unique_lock<std::mutex> lck(mtx); // get mutex
static vector<double> previous_measure = this->position; // initialized with the first position the robot is in
lck.unlock();
while (this->moving) {
lck.lock();
auto auxiliar = this->position;
lck.unlock();
this->area_covered += distance(auxiliar , previous_measure) * this->width;
previous_measure = this->position; // save the new position for the next iteration
this_thread::sleep_for(chrono::milliseconds(10)); // sleep for 10 ms
std::cout << "Area:" << this->area_covered << " m²" << std::endl;
}
}
void next_step() {
std::unique_lock<std::mutex> lck(mtx); // get mutex
this->position[0] += DT * this->direction[0];
this->position[1] += DT * this->direction[1];
}
void move_robot(const vector<vector<double> > &map) {
// accumulate the footprint while the robot moves
// Iterate through the path
for (unsigned int i=1; i < map.size(); i++) {
while (distance(position , map[i]) > DISTANCE_TOLERANCE ) {
this->direction = unitary_vector(map[i], this->position);
this->next_step();
this_thread::sleep_for(chrono::milliseconds(10)); // sleep for 10 ms
}
}
this->moving = false; // notify get area to leave
}
In the get_area()
method I am locking to save the this->position
attribute in a variable and have the same one over the whole iteration and to initialize the first previous_measure
.在
get_area()
方法中,我锁定以将this->position
属性保存在变量中,并在整个迭代中具有相同的属性并初始化第一个previous_measure
。 In next_step()
is used when this->position
changes according the dynamics of the vehicle.当
this->position
根据车辆的动态变化时,在next_step()
中使用。
Moreover, how would you lock the moving
attribute inside the while condition?此外,您如何将
moving
属性锁定在 while 条件中? And how to avoid this situation: I execute get_area()
, the robot moves and ends the path and I leave the get_area
method without doing the last iteration.以及如何避免这种情况:我执行
get_area()
,机器人移动并结束路径,我离开get_area
方法而不进行最后一次迭代。
My question is whether it would be possible to optimize this locking and unlocking , as it is done pretty often (I do not want to set a producer-client structure because I want get_area() to run independent).我的问题是是否可以优化这种锁定和解锁,因为它经常进行(我不想设置生产者-客户端结构,因为我希望 get_area() 独立运行)。
I have changed the condition in the while to:我已经将条件更改为:
bool is_moving() {
std::unique_lock<std::mutex> lck(mtx);
return moving;
}
void get_area() {
std::unique_lock<std::mutex> lck(mtx);
static vector<double> previous_measure = this->position;
lck.unlock();
while (this->is_moving()) {...}
}
But do not know if there is something even cleaner但不知道有没有更干净的东西
Considering the speed of the robot, there is no real need to lock both the position
and moving
since the rate of change of this values, compared to the rate of computation will be enormously small, so the margin of error due to stale
read will be negligible.考虑到机器人的速度,实际上并没有必要同时锁定
position
和moving
,因为这个值的变化率与计算速度相比将非常小,因此由于stale
读取导致的误差范围将是微不足道。
In any case the above code is doing exactly that, since the previous_measure = this->position
is reading the position
while it's unlocked, therefore there is small chance that previous_measure != auxiliar
at the end of the loop.在任何情况下,上面的代码都是这样做的,因为
previous_measure = this->position
在position
解锁时读取它,因此在循环结束时previous_measure != auxiliar
的可能性很小。
To assure that a final read is executed (meaning get_area
is computed again after moving
is false), we can repeat the calculation once more after leaving the loop, since then the robot had stoped moving and the position will never change.为了确保执行最终读取(意味着
get_area
在moving
为假后再次计算),我们可以在离开循环后再次重复计算,因为那时机器人已经停止移动并且 position 永远不会改变。
In case the speed of change of the variables is close to the speed of computiation, the optimization #1 will obviously create significant error.如果变量的变化速度接近计算速度,优化#1显然会产生很大的误差。 In such case, you can consider using ring buffer to move the values from
move_robot
to get_area
without locking.在这种情况下,您可以考虑使用环形缓冲区将值从
move_robot
移动到get_area
而无需锁定。 If you have only one consumer and only one producer thread, there is no need to lock.如果你只有一个消费者,只有一个生产者线程,则不需要加锁。 Here is an example of implementation of ring buffer (or circular buffer).
这是一个实现环形缓冲区(或循环缓冲区)的示例。
In the first example you're reading this->moving
while not holding the lock.在第一个示例中,您正在阅读
this->moving
而没有持有锁。 This will lead to UB if another thread is also writing it.如果另一个线程也在编写它,这将导致 UB。 You can reorder the locking a bit to make it correct, for example like this:
您可以重新排序锁定以使其正确,例如:
void get_area() {
std::unique_lock<std::mutex> lck(mtx); // get mutex
static vector<double> previous_measure = this->position; // initialized with the first position the robot is in
while (this->moving) {
auto auxiliar = this->position;
this->area_covered += distance(auxiliar , previous_measure) * this->width;
previous_measure = this->position; // save the new position for the next iteration
lck.unlock();
this_thread::sleep_for(chrono::milliseconds(10)); // sleep for 10 ms
std::cout << "Area:" << this->area_covered << " m²" << std::endl;
lck.lock();
}
}
Also, move_robot
must also hold the lock while updating shared values.此外,
move_robot
在更新共享值时也必须持有锁。 Just make sure to not hold the lock while sleeping.只要确保在睡觉时不要握住锁。
void move_robot(const vector<vector<double> > &map) {
std::unique_lock<std::mutex> lck(mtx);
for (unsigned int i=1; i < map.size(); i++) {
while (distance(position , map[i]) > DISTANCE_TOLERANCE ) {
this->direction = unitary_vector(map[i], this->position);
this->next_step();
lck.unlock();
this_thread::sleep_for(chrono::milliseconds(10)); // sleep for 10 ms
lck.lock();
}
}
this->moving = false; // notify get area to leave
}
Remember, if writing is involved, then all parallel access to a variable must either be protected by a mutex or the variable must be made atomic
.请记住,如果涉及写入,则对变量的所有并行访问都必须受到互斥锁的保护,或者必须使变量成为
atomic
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.