所以我对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(),但是,我的理解是"强"变体不会失败。所以我调试了它,它总是我的向量的第三个元素被跳过(即使在单个线程上工作时)。
我已经在不同的计算机(和架构)上测试了同样的东西,但同样的问题仍然存在,所以我猜问题是逻辑。
对正在发生的事情有什么想法吗?
地雷危险教育
#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:
<...>将存储在
*this
中的实际值加载到expected
(执行加载操作)。
让我们向量busy
成为繁忙状态的条件名称。 第一个get_connection()
调用会导致向量busy
被{ true, false, ... false }
。
第二个get_connection()
调用:
expected = false;
busy[0]
true
不等于expected
,它就会得到expected = true;
busy[1]
false
不等于更新的expected
,它会得到expected = false;
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()
调用不再修改向量:
- <...>
busy[10]
true
不等于expected
,它就会得到expected = true;
busy[11]
true
等于更新的expected
,返回。
修复
// 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
}