cpp compare_exchange_strong 虚假地失败了?

cpp compare_exchange_strong fails spuriously?

所以我是 CPP 的新手,我正在尝试为我正在开发的一个小项目实施资源池(SQLITE 连接)。

问题是我有一个列表(向量),其中包含在程序开始时创建的对象,这些对象具有给定的连接及其可用性 (atomic_bool)。如果我的程序请求来自该池的连接,则会执行以下函数:

gardener_db &connection_pool_inner::get_connection() {
    bool expected = false;

    for(auto & pair : pool) {
        std::atomic_bool &ref = pair->get_is_busy_ref();
        if (ref.compare_exchange_strong(expected, true)) {
            return pair->get_conn();
        }//if false it means that its busy
    }
    std::lock_guard<std::mutex> guard(vector_mutex);//increment size
    std::atomic_bool t = true;
    pool.emplace_back(std::make_shared<pool_elem>());
    pool.back()->get_is_busy_ref().store(t);// because we are giving the resource to the caller
    return pool.back()->get_conn();
}

所以我做了一个简单的测试,看看我的矢量是否正在调整大小:

constexpr unsigned int CONNECTION_POOL_START_SIZE = 20;
TEST(testConnPool, regrow) {
    auto saturation = std::vector<connection_handler>();
    ASSERT_EQ(saturation.size(), 0);
    for(int i = 0; i < CONNECTION_POOL_START_SIZE; i++) {
        saturation.emplace_back();
    }
    auto ptr = connection_pool_inner::instance();
    auto size = ptr->get_pool().size();

    //it  should be full at this point
    ASSERT_EQ(size, CONNECTION_POOL_START_SIZE);
}

问题是我的尺码是 22,而不是我期望的 20。

我能够将问题缩小到 compare_exchage_strong() 但是,我的理解是“强”变体不会失败。所以我调试了它,它总是被跳过的向量的第三个元素,(即使在单个线程上工作)。

我已经在不同的计算机(和体系结构)上测试过同样的东西,但同样的问题仍然出现,所以我猜问题出在逻辑上。

对正在发生的事情有什么想法吗?

MRE

#include <mutex>
#include <atomic>
#include <iostream>
#include <vector>
#include <memory>

class foo {
    std::atomic_bool inner;
public:
    explicit foo(): inner(false){};
    std::atomic_bool &get(){return inner;}
};

std::mutex vector_mutex;
std::vector<std::shared_ptr<foo>> resources;

void get_resource() {
    bool expected = false;
    for(auto & rsc : resources) {
        if (rsc->get().compare_exchange_strong(expected, true)) {
            return;
        }
    }
    std::lock_guard<std::mutex> guard(vector_mutex);//increment size
    resources.emplace_back(std::make_shared<foo>());
}

int main() {
    std::vector<std::shared_ptr<foo>> *local = &resources;
    for(int i = 0; i < 20; i++) {
        resources.emplace_back(std::make_shared<foo>());
    }
    for(int i = 0; i < 20; i++) {
        get_resource();
    }
    std::cout << resources.size();
}

该代码在多线程环境中导致未定义的行为。

虽然循环 for(auto & pair : pool) 在一个线程中运行,但在另一个线程中的 pool.emplace_back(std::make_shared<pool_elem>()) 会使 pool 循环中使用的 运行 迭代器在幕后失效。

你的循环出错了。 std::atomic::compare_exchange_strong:

<...> loads the actual value stored in *this into expected (performs load operation).

让向量 busy 成为繁忙状态的条件名称。 第一个 get_connection() 调用导致向量 busy{ true, false, ... false }

第二个get_connection()调用:

  1. expected = false;
  2. busy[0]true不等于expected,得到expected = true;
  3. busy[1]false不等于更新后的expected,得到expected = false;
  4. busy[2]false 等于更新后的 expected,导致向量 busy{ true, false, true, false, ... false }

另外 8 次 get_connection() 调用导致向量 busy 成为 { (true, false) * 10 }

第 11 次和第 12 次 get_connection() 调用添加了一对 true 并导致向量 busy{ (true, false) * 10, true, true },大小为 22。

其他get_connection()调用不再修改向量:

  1. <...>
  2. busy[10]true不等于expected,得到expected = true;
  3. busy[11]true等于更新后的expected,return.

修复

// bool expected = false;  ───────┐
//                                │
for(auto & pair : pool) {   //    │
    bool expected = false;  // ◄──┘
    std::atomic_bool &ref = pair->get_is_busy_ref();
    if (ref.compare_exchange_strong(expected, true)) {
        return pair->get_conn();
    }//if false it means that its busy
}