我在我的一个项目中有一个特定的需求,那就是保持某些操作的“计数”,并定期(例如24小时)“读取”+“重置”这些计数器。
操作将是:
- 工作线程 -> 增加计数器(随机)
- 计时器线程(例如24小时) -> 读取计数器 -> 做某事 -> 重置计数器
我感兴趣的平台是Windows,但如果这可以跨平台更好。 我正在使用Visual Studio,并且目标Windows架构仅为 x64 。
我不确定结果是否“正确”,以及我的实现是否正确。坦白地说,我从未使用过许多std包装器,而且我的c++知识相当有限。
结果为:
12272 Current: 2
12272 After: 0
12272 Current: 18
12272 After: 0
12272 Current: 20
12272 After: 0
12272 Current: 20
12272 After: 0
12272 Current: 20
12272 After: 0
以下是一个完全可复制/粘贴的可重现示例:
#include <iostream>
#include <chrono>
#include <thread>
#include <Windows.h>
class ThreadSafeCounter final
{
private:
std::atomic_uint m_Counter1;
std::atomic_uint m_Counter2;
std::atomic_uint m_Counter3;
public:
ThreadSafeCounter(const ThreadSafeCounter&) = delete;
ThreadSafeCounter(ThreadSafeCounter&&) = delete;
ThreadSafeCounter& operator = (const ThreadSafeCounter&) = delete;
ThreadSafeCounter& operator = (ThreadSafeCounter&&) = delete;
ThreadSafeCounter() : m_Counter1(0), m_Counter2(0), m_Counter3(0) {}
~ThreadSafeCounter() = default;
std::uint32_t IncCounter1() noexcept
{
m_Counter1.fetch_add(1, std::memory_order_relaxed) + 1;
return m_Counter1;
}
std::uint32_t DecCounter1() noexcept
{
m_Counter1.fetch_sub(1, std::memory_order_relaxed) - 1;
return m_Counter1;
}
VOID ClearCounter1() noexcept
{
m_Counter1.exchange(0);
}
};
int main()
{
static ThreadSafeCounter Threads;
auto Thread1 = []() {
while (true)
{
auto test = Threads.IncCounter1();
std::cout << std::this_thread::get_id() << " Threads.IncCounter1() -> " << test << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
}
};
auto Thread2 = []() {
while (true)
{
auto test = Threads.DecCounter1();
std::cout << std::this_thread::get_id() << " Threads.DecCounter1() -> " << test << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
}
};
auto Thread3 = []() {
while (true)
{
Threads.ClearCounter1();
std::cout << std::this_thread::get_id() << " Threads.ClearCounter1()" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
}
};
std::thread th1(Thread1);
std::thread th2(Thread2);
std::thread th3(Thread3);
th1.join();
th2.join();
th3.join();
}
我应该提到,在我的实际项目中并未使用std::thread包装器,线程是使用WinApi函数(例如CreateThread)创建的。上面的代码只是为了模拟/测试。
请指出上面的代码有什么错误,可以改进什么,是否完全正确。
谢谢!
IncCounter1()
这样的函数是原子的,但它们并不是。你看到的行为可能的一个解释是一个线程正在调用IncCounter1()
,在调用m_Counter1.fetch_add()
之后但在到达return
语句之前被其他线程调用DecCounter1()
和ClearCounter1()
打断了。将IncCounter1()
更改为类似于return m_Counter1.fetch_add(1, std::memory_order_relaxed) + 1
的内容。其他操作也同理。 - Peter