创建线程安全原子计数器

Nor*_*ros 4 c++ windows

我在我的一个项目中有一个特定的要求,即保持某些操作的“计数”,并最终定期“读取”+“重置”这些计数器(例如24小时)。

操作将是:

  • 工作线程 -> 递增计数器(随机)
  • 计时器线程(例如 24 小时)-> 读取计数 -> 执行某些操作 -> 重置计数器

我感兴趣的平台是Windows,但是如果这个可以跨平台就更好了。我正在使用 Visual Studio,目标 Windows 架构仅是 x64

我不确定结果是否“ok”以及我的实施是否正确。坦率地说,我从未使用过太多 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
Run Code Online (Sandbox Code Playgroud)

下面是一个完全复制/粘贴的可重现示例:

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
Run Code Online (Sandbox Code Playgroud)

我应该提到,在我的现实项目中,没有使用 std::thread 包装器,并且线程是使用 CreateThread 等 WinApi 函数创建的。以上只是模拟/测试代码。

请向我指出上面的代码有什么问题,可以改进什么,以及我的方向是否正确。

谢谢你!

Haj*_*off 11

你为什么要写一ThreadSafeCounter堂课?

std::atomic<size_t> 一个线程安全计数器。这就是 std::atomic 的全部意义。所以你应该改用它。不需要再上一堂课。大多数原子都有operator++/operator--专业化,所以你的主循环可以很容易地重写如下:

    static std::atomic_int ThreadCounter1(0);

    auto Thread1 = []() {
        while (true)
        {
            auto test = ++ThreadCounter1;  // or ThreadCounter1++, whatever you need
            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 = --ThreadCounter1;
            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)
        {
/* Note: You could simply "ThreadCounter1 = 0" assign it here.
But exchange not only assigns a new value, it returns the previous value.
*/
            auto ValueAtReset=ThreadCounter1.exchange(0);
            std::cout << std::this_thread::get_id() << " Threads.ClearCounter1() called at value" << ValueAtReset << std::endl;

            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
    };

Run Code Online (Sandbox Code Playgroud)

我忘了提及您的 DecCounter 操作的问题。您正在使用atomic_uint,它无法处理负数。但不能保证您的 Thread2 不会在 Thread1 之前运行(也称为递减计数器)。这意味着你的柜台将会换行。

所以你可以/应该使用std::atomic<int>。这将为您提供正确的数量(calls_Thread1 - Calls_Thread2)。如果 Thead2 比 Thread1 更频繁地递减该值,则该数字将变为负数。

  • 关于内存顺序,你当然是对的。但是,除非性能确实至关重要 - *并且* inc/dec 操作已被证明是罪魁祸首 - 为了清楚起见,我仍然建议使用运算符++/--。我也不想让事情变得复杂。内存顺序的复杂性很难掌握,值得额外提出一个 stackoverflow 问题:) (3认同)