【问题标题】:Thread safe std::cout线程安全的 std::cout
【发布时间】:2017-04-30 09:38:31
【问题描述】:

以下程序仍将输出交错到std::cout。我试图通过std::lock_guardadd a std::mutex to control access 到std::cout,但它是still interleaves。

#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>

std::mutex global_mtx{};

class Timer {
public:
    Timer(size_t time, const std::function<void(void)>& f) : time{std::chrono::milliseconds{time}}, f{f} {}
    ~Timer() { wait_thread.join(); }

private:
    void wait_then_call()
    {
        std::unique_lock<std::mutex> lck{mtx};
        for(int i{10}; i > 0; --i) {
            {
                std::lock_guard<std::mutex>{global_mtx};
                std::cout << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;

            }
            cv.wait_for(lck, time / 10);
        }
        f();
    }
    std::mutex mtx;
    std::condition_variable cv{};
    std::chrono::milliseconds time;
    std::function <void(void)> f;
    std::thread wait_thread{[this]() {wait_then_call(); }};
};

int main()
{
    auto f = []() {std::lock_guard<std::mutex>{global_mtx}; std::cout << "---------------- I waited to print! ----------------" << std::endl; };
    Timer t1{3'000,f};
    Timer t2{6'000,f};
    Timer t3{2'000,f};
    Timer t4{1'000,f};
}

我是否需要通过单独的类或专用线程来控制访问?

【问题讨论】:

    标签: c++ thread-safety cout


    【解决方案1】:

    你的问题在这里:std::lock_guard&lt;std::mutex&gt;{global_mtx}; 创建一个锁守卫并立即释放它。你需要创建一个变量来持有锁,比如std::lock_guard&lt;std::mutex&gt; lock{global_mtx};。

    【讨论】:

      【解决方案2】:

      防止忘记命名锁的一种方法是创建一个可以用作 io 操纵器的锁对象:

      #include <iostream>
      #include <chrono>
      #include <thread>
      #include <functional>
      #include <mutex>
      #include <condition_variable>
      
      std::mutex global_mtx{};
      
      struct lockio
      {
          lockio(std::mutex& m) : lock_(m) {}
      
          std::unique_lock<std::mutex> lock_;
      };
      std::ostream& operator<<(std::ostream& os, const lockio&) {
          return os;
      }
      
      class Timer {
      public:
          Timer(size_t time, const std::function<void(void)>& f) : time{std::chrono::milliseconds{time}}, f{f} {}
          ~Timer() { wait_thread.join(); }
      
      private:
          void wait_then_call()
          {
              std::unique_lock<std::mutex> lck{mtx};
              for(int i{10}; i > 0; --i) {
                  {
                      std::cout << lockio(global_mtx) << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
      
                  }
                  cv.wait_for(lck, time / 10);
              }
              f();
          }
          std::mutex mtx;
          std::condition_variable cv{};
          std::chrono::milliseconds time;
          std::function <void(void)> f;
          std::thread wait_thread{[this]() {wait_then_call(); }};
      };
      
      int main()
      {
          auto f = []() { std::cout << lockio(global_mtx) << "---------------- I waited to print! ----------------" << std::endl; };
          Timer t1{3'000,f};
          Timer t2{6'000,f};
          Timer t3{2'000,f};
          Timer t4{1'000,f};
      }
      

      另一种(可能更好)的方法是创建一个小帮助模板函数来包装受保护的操作:

      #include <iostream>
      #include <thread>
      #include <condition_variable>
      
      std::mutex global_mtx{};
      
      template<class Mutex, class F>
      decltype(auto) with_lock(Mutex &m, F &&f) {
          std::lock_guard<Mutex> lock(m);
          return f();
      };
      
      class Timer {
      public:
          Timer(size_t time, const std::function<void(void)> &f) : time{std::chrono::milliseconds{time}}, f{f} {}
      
          ~Timer() { wait_thread.join(); }
      
      private:
          void wait_then_call() {
              std::unique_lock<std::mutex> lck{mtx};
              for (int i{10}; i > 0; --i) {
                  with_lock(global_mtx, [&] {
                      std::cout << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
                  });
                  cv.wait_for(lck, time / 10);
              }
              f();
          }
      
          std::mutex mtx;
          std::condition_variable cv{};
          std::chrono::milliseconds time;
          std::function<void(void)> f;
          std::thread wait_thread{[this]() { wait_then_call(); }};
      };
      
      int main() {
          auto f = []() {
              with_lock(global_mtx, []
              {
                  std::cout << "---------------- I waited to print! ----------------" << std::endl;
              });
          };
          Timer t1{3'000, f};
          Timer t2{6'000, f};
          Timer t3{2'000, f};
          Timer t4{1'000, f};
      }
      

      另一种方式:

      #include <iostream>
      #include <thread>
      #include <condition_variable>
      
      
      struct locked {
      
          std::ostream& cout() const { return std::cout; }
          std::ostream& cerr() const { return std::cerr; }
      
      private:
          static std::mutex& mutex() {
              static std::mutex stdio_mutex;
              return stdio_mutex;
          }
          std::unique_lock<std::mutex> lock_{mutex()};
      };
      
      class Timer {
      public:
          Timer(size_t time, const std::function<void(void)> &f) : time{std::chrono::milliseconds{time}}, f{f} {}
      
          ~Timer() { wait_thread.join(); }
      
      private:
          void wait_then_call() {
              std::unique_lock<std::mutex> lck{mtx};
              for (int i{10}; i > 0; --i) {
                  locked().cout() << "Thread " << wait_thread.get_id() << " countdown at: " << '\t' << i << std::endl;
                  cv.wait_for(lck, time / 10);
              }
              f();
          }
      
          std::mutex mtx;
          std::condition_variable cv{};
          std::chrono::milliseconds time;
          std::function<void(void)> f;
          std::thread wait_thread{[this]() { wait_then_call(); }};
      };
      
      int main() {
          auto f = []() {
              locked().cout() << "---------------- I waited to print! ----------------" << std::endl;
          };
          Timer t1{3'000, f};
          Timer t2{6'000, f};
          Timer t3{2'000, f};
          Timer t4{1'000, f};
      }
      

      【讨论】:

      • 两者都有用。对于这个问题,lockio 似乎更干净,对于我来说,works 将互斥锁作为静态成员变量。第二个模板选项当然具有适用于任何功能的好处。
      • 第三个清楚地说明了发生了什么。我喜欢它用来在struct 中定义静态互斥锁的魔法。
      【解决方案3】:

      您创建了四个 Timer 对象,每个对象都有自己唯一的互斥对象。所以当 t2 运行它的线程时,它会锁定自己的互斥锁,因为 t1 在开始循环之前锁定了另一个互斥锁。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-21
        • 2022-01-05
        • 1970-01-01
        • 1970-01-01
        • 2011-09-16
        • 1970-01-01
        • 2013-02-10
        相关资源
        最近更新 更多