是否使用类包装器对符合C ++ 11的对象进行线程安全访问?

时间:2013-08-24 14:32:06

标签: c++ multithreading c++11

我是否可以使用此类包装器来对对象进行线程安全访问,并且所需的行为符合C ++ 11?

字符串的主要重点:

T* operator->() {

T& operator*() {

注意,我知道这里最好使用std :: atomic<>对于整数(int),但在此代码而不是int中,我们可以使用任何其他对象。

使用execute-around pointer idiom版本2.0:

#include<thread>
#include<mutex>
#include<memory>
#include<iostream>
#include<vector>

template<typename T>
class safe_obj {
    T obj;
    mutable std::mutex mtx;
    safe_obj(safe_obj<T> &) {}
    safe_obj<T>& operator=(safe_obj<T> &) {}

    class T_exclusive_lock {
         std::unique_lock<std::mutex> xlock;
    public:
         T*const self;
         T_exclusive_lock(T * const s, std::mutex& _mtx)
             :self(s), xlock(_mtx) {}

        T* operator -> () const {return self;}
        operator T&()  {return *self;}

        friend std::ostream& operator << (std::ostream &stream, T_exclusive_lock &obj) {
            stream << obj;
            return stream;
        }
    };

public:
    template<typename  Types>
    safe_obj(Types  args) : obj(args ) 
    { }

    T_exclusive_lock operator->() {
        return T_exclusive_lock(&obj, mtx);
    }    

    T_exclusive_lock* operator*() {
        return &T_exclusive_lock(&obj, mtx);
    }       
};

int main() {
    safe_obj<std::shared_ptr<int> > safe_int( std::make_shared<int>(10) );   

    auto lambda = [&safe_int]() {
        std::cout << safe_int->use_count() << std::endl;    // is that thread-safe? 
        std::cout << *safe_int << std::endl;    // is that thread-safe? 
        std::cout << *safe_int->get() << std::endl;    // is that thread-safe? 
    };

    std::vector<std::thread> thr_grp;
    for(size_t i = 0; i < 10; ++i) thr_grp.emplace_back(std::thread(lambda));
    for(auto &i : thr_grp) i.join();

    int b; std::cin >> b;
    return 0;
}

3 个答案:

答案 0 :(得分:8)

您提供的原始代码并不保证任何线程安全。你的std::unique_lock一旦超出范围就会解锁互斥锁,这是在使用你想要保护的对象之前。

要获得所需的结果,您需要声明另一个模板化的类(例如locked_obj<T>),它将表示处于锁定状态的对象(通过unique_lock上的safe_obj 'sutex)并从重载的运算符返回这样的对象。这样的对象是临时对象,允许您在locked_obj的生命周期内操纵受保护的对象。由于临时对象一直存在到当前语句结束,因此使用此类safe_obj将大部分是透明的。

这种技术是execute-around pointer成语的应用。

答案 1 :(得分:2)

不,这不是线程安全的。当访问者函数返回时,互斥锁已被解锁,因此不会同步对对象的访问。

一种方法是重载赋值和转换运算符:

safe_obj & operator=(T const &t) {
    std::unique_lock<std::mutex> lock(mtx);
    obj = t;
    return *this;
}

operator T() {
    std::unique_lock<std::mutex> lock(mtx);
    return obj;
}

但如果您想提供所有复合赋值运算符,这可能会变得乏味,如果对象不可复制,则转换运算符将不起作用。

另一种方法是返回一个包含unique_lock的访问者对象,只要您有权访问该对象,就会锁定互斥锁。

答案 2 :(得分:2)

锁定单个功能或锁定对单个对象的访问并不能保证线程安全;根据程序的不同,许多操作需要对同一对象进行多次函数调用,而不会中断或对多个对象进行操作而不会中断。线程安全必须设计到应用程序中;没有库黑客可以使应用程序没有正确设计为线程安全的。