【发布时间】:2022-01-23 04:35:54
【问题描述】:
我正在尝试通过std::atomic<std::shared_ptr>> 实现无锁包装器,以对容器等重要对象进行操作。
我在这两个主题中找到了一些相关的信息:
但这仍然不是我需要的。
举个例子:
TEST_METHOD(FechAdd)
{
constexpr size_t loopCount = 5000000;
auto&& container = std::atomic<size_t>(0);
auto thread1 = std::jthread([&]()
{
for (size_t i = 0; i < loopCount; i++)
container++;
});
auto thread2 = std::jthread([&]()
{
for (size_t i = 0; i < loopCount; i++)
container++;
});
thread1.join();
thread2.join();
Assert::AreEqual(loopCount * 2, container.load());
}
这个函数可以正常工作,因为后自增运算符使用了内部fetch_add() 原子操作。
另一方面:
TEST_METHOD(LoadStore)
{
constexpr size_t loopCount = 5000000;
auto&& container = std::atomic<size_t>(0);
auto thread1 = std::jthread([&]()
{
for (size_t i = 0; i < loopCount; i++)
{
auto value = container.load();
value++;
container.store(value);
}
});
auto thread2 = std::jthread([&]()
{
for (size_t i = 0; i < loopCount; i++)
{
auto value = container.load();
value++;
container.store(value);
}
});
thread1.join();
thread2.join();
Assert::AreEqual(loopCount * 2, container.load());
}
而如果我将其替换为 .load() 和 .store() 操作并在这两个操作之间递增,结果就不一样了。
那是两个原子操作,因此这些操作之间无法进行同步。
我的最终目标是通过std::atomic<std::shared_ptr> 加载对象的实际状态,执行一些非常量操作,并通过存储操作再次保存。
TEST_METHOD(AtomicSharedPtr)
{
constexpr size_t loopCount = 5000000;
auto&& container = std::atomic(std::make_shared<std::unordered_set<int>>());
auto thread1 = std::jthread([&]([[maybe_unused]] std::stop_token token)
{
for (size_t i = 0; i < loopCount; i++)
{
// some other lock-free synchronization primitives as barrier, conditions or?
auto reader = container.load();
reader->emplace(5);
container.store(reader);
}
});
auto thread2 = std::jthread([&]([[maybe_unused]] std::stop_token token)
{
for (size_t i = 0; i < loopCount; i++)
{
// some other lock-free synchronization primitives as barrier, conditions or?
auto reader = container.load();
reader->erase(5);
container.store(reader);
}
});
}
我知道第二个线程也只有 shared_ptr 来自对 shared_ptr 的原子和非常量操作,which can only cause data race.
那么关于如何实现一个无锁包装器的任何提示,该包装器将与存储在std::atomic<std::shared_ptr> 中的对象的非常量操作一起工作?
【问题讨论】:
-
如果您正在寻找一种方法来使多线程访问任何 C++ 类线程安全而无需锁定,我认为这是不可能的。如果您正在寻找更具体的内容,您可能需要澄清它是什么。
-
read-modify-write 不能以原子方式对任意数据执行而不会失败。您要么需要添加一个重试循环来处理故障(困难、缓慢且可能有错误),要么使用锁(简单、快速、可能没有错误)。
-
从标准锁定机制开始。如果分析显示锁阻止程序达到性能目标,那么继续做一些偷偷摸摸的事情。如果代码达到性能目标,谁会在乎是否会比最优理想慢一点?
-
@user4581301 我已经完成了使用锁定模式的模板包装器,尤其是 std::shared_mutex,在每个 stl 容器上创建并发,但现在我正在寻找任何方法来做到这一点如果存在则不加锁。
-
@VítJaneček:你真的不应该尝试“在每个 stl 容器上创建并发”。良好的并发性需要比单纯的容器更粗粒度和更高级别。并发需要保证数据的含义,这可能涉及需要保持同步的两个或多个容器。正如您所看到的,两个连续的原子操作(无论是否有锁)都不是原子的。基本上,您的并发方法只是在乞求死锁。
标签: c++ multithreading c++20 stdatomic