简体   繁体   English

线程安全std :: cout

[英]Thread safe std::cout

The following program still interleaves the output to std::cout . 以下程序仍将输出交织到std::cout I tried to add a std::mutex to control access to std::cout via std::lock_guard , but it still interleaves . 我试图添加一个std::mutex来控制通过std::lock_guardstd::cout 访问 ,但是它仍然是交错的

#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};
}

Do I need to control access through a separate class or dedicated thread? 我是否需要通过单独的类或专用线程来控制访问?

Your problem is here: std::lock_guard<std::mutex>{global_mtx}; 您的问题在这里: std::lock_guard<std::mutex>{global_mtx}; creates a lock guard and immediately releases it. 创建一个锁卫并立即释放它。 You need to create a variable to hold the lock, like std::lock_guard<std::mutex> lock{global_mtx}; 您需要创建一个变量来保存锁,例如std::lock_guard<std::mutex> lock{global_mtx}; .

One way to prevent forgetting to name the lock is to make a lock object that you can use as an io manipulator: 防止忘记命名锁的一种方法是制作一个可用作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};
}

Another (probably better) way is to create a little helper template function to wrap the protected operations: 另一种(可能更好)的方法是创建一个小的帮助程序模板函数来包装受保护的操作:

#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};
}

one more way: 另一种方式:

#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};
}

You create four Timer objects, each one having its own unique mutex object. 您创建四个Timer对象,每个对象都有自己的唯一互斥对象。 So when t2 runs its thread, it locks its own mutex and it can because t1 locked a different mutex before beginning its loop. 因此,当t2运行其线程时,它将锁定自己的互斥锁,并且可以这样做是因为t1在开始其循环之前已锁定了另一个互斥锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM