单例模板类以同时交换T对象

时间:2019-07-19 15:20:39

标签: c++ multithreading templates

Exchanger应该提供一种交换方法,让两个线程立即交换它们的对象。即使没有死锁,某些线程也会得到错误的结果。通过调试,我发现两个指针之一的值被随机地改变了一个,我不明白为什么。

#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>
#include <sstream>
#include <vector>
#include <random>
#include <queue>

template <typename T>
class Exchanger {
    inline static Exchanger* instance;
    inline static std::once_flag inited;
    std::mutex mutex;
    std::condition_variable to_exchange;
    T* p1 = nullptr;
    T* p2 = nullptr;
    bool ready = false;

    Exchanger(){};
    ~Exchanger(){};

public:
    static Exchanger* getInstance() {
        std::call_once(inited, []{
            instance = new Exchanger();
        });
        return instance;
    }

    T exchange(T t) {
        std::unique_lock lock(mutex);
        if (!ready) {
            p1 = &t;
            ready = true;
            to_exchange.wait(lock, [this]{return !ready;});
            return *p2;
        } else {
            p2 = &t;
            ready = false;
            to_exchange.notify_one();
            return *p1;
        }

    }

};

int* p = nullptr;

int exchange(int t) {
    p = &t;
}

int main(int argc, char **argv) {

    int x = 10;
    exchange(x);
    std::cout << *p << std::endl;

    std::vector<std::thread> traders;
    for (int i = 0; i < 4; i++) {
        traders.emplace_back([i]{
            std::this_thread::sleep_for(std::chrono::seconds(rand() % 2));
            std::stringstream msg1;
            msg1 << "thread " << std::this_thread::get_id() << " willing to trade " << i << std::endl;
            std::cout << msg1.str();

            std::stringstream msg2;
            msg2 << "thread " << std::this_thread::get_id() << " got " << Exchanger<int>::getInstance()->exchange(i) << std::endl;
            std::cout << msg2.str();
        });
    }

    for (int i = 4; i < 8; i++) {
        traders.emplace_back([i]{
            std::this_thread::sleep_for(std::chrono::seconds(rand() % 2));
            std::stringstream msg1;
            msg1 << "thread " << std::this_thread::get_id() << " willing to trade " << i << std::endl;
            std::cout << msg1.str();

            std::stringstream msg2;
            msg2 << "thread " << std::this_thread::get_id() << " got " << Exchanger<int>::getInstance()->exchange(i) << std::endl;
            std::cout << msg2.str();
        });
    }

    for (auto &t: traders) {
        if (t.joinable()) {
            t.join();
        }
    }

    return 0;
}

此外,int exchange是否应该在函数末尾销毁t<int>参数?为什么int* p仍然指向实际上是副本的传递值?

1 个答案:

答案 0 :(得分:0)

  

通过调试,我看到两个指针之一获得了它的值   改变了一些随机的,我不明白为什么

p1p2将地址存储到函数的局部变量:

    T exchange(T t) {
    std::unique_lock lock(mutex);
    if (!ready) {
        p1 = &t; // <------HERE - STORING ADDRESS TO LOCAL VARIABLE
        ready = true;
        to_exchange.wait(lock, [this]{return !ready;});
        return *p2;
    } else {
        p2 = &t; // <------HERE - STORING ADDRESS TO LOCAL VARIABLE
        ready = false;
        to_exchange.notify_one();
        return *p1;
    }

}

函数结束时,此局部变量超出范围,导致p1p2成为悬空指针。访问悬空指针是未定义的行为,这也解释了调试时获得的随机值。 而不是存储变量的地址store a copy