【问题标题】:Is using the class-wrapper for thread-safe access to the object conform to C++11?使用类包装器对对象进行线程安全访问是否符合 C++11?
【发布时间】:2013-08-24 14:32:05
【问题描述】:

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

字符串的主要重音:

T* operator->() {

T& operator*() {

注意,我知道这里最适合使用 std::atomic 作为整数(int),但在这段代码中,我们可以使用任何其他对象而不是 int。

2.0 版,使用execute-around pointer idiom

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

【问题讨论】:

  • 我建议 std::shared_ptr 和 shared_ptr 原子访问

标签: c++ multithreading c++11


【解决方案1】:

您提供的原始代码不保证任何线程安全。您的std::unique_locks 在超出范围后立即解锁互斥锁,这是在使用您要保护的对象之前。

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

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

【讨论】:

  • 谢谢。我已经通过使用执行指针习语编辑到新版本,这样正确吗?
  • 是的,你正确地应用了这个成语。但是,我不确定您要在 static_cast&lt;std::shared_ptr&lt;int&gt;&gt;(*safe_int) 行中实现什么...
  • 但是为什么Boost-library中不存在或存在这种带有执行指针习语的模板化指针?
  • 我在 Boost 中没有注意到它。为什么不在那里?也许它还没有(因为没有人愿意把它放在一边)或者也许有人考虑过它并由于Pete Becker在他的回答中规定的原因而拒绝了它。在某些情况下,使用这样的习语可能会导致错误的线程安全感。此外,请注意您的实现可能有一个隐含的警告 - 在同一语句中多次取消引用相同的 safe_obj 会导致死锁。这很容易克服(例如,通过返回 shared_ptr),但这会导致性能轻微下降......
  • 避免死锁,使用 std::recursive_mutex 很容易。这个指针的性能对于大多数对速度不重要的情况来说已经足够了。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    不,这不是线程安全的。在访问器函数返回时,互斥锁已解锁,因此对对象的访问并未同步。

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

    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 的访问器对象,只要您有权访问该对象,它就会保持互斥锁锁定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-14
      • 2016-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-12
      • 1970-01-01
      相关资源
      最近更新 更多