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()
调用:
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
,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
}
所以我是 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
intoexpected
(performs load operation).
让向量 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
,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
}