2015-09-26 45 views
3

编辑:我搬到这个问题代码审查https://codereview.stackexchange.com/questions/105742/thread-safe-holder线程安全的持有人

我实现了一个线程安全的持有人在线程之间安全地传递数据。

用户可以多次设置值,但只有第一个SetIfEmpty调用存储该值,则用户可以多次读取该值。

template <typename T> 
class ThreadSafeHolder { 
public: 
    ThreadSafeHolder() : is_value_set_(false) { 
    } 

    void SetIfEmpty(const T& value) { 
     std::lock_guard<std::mutex> lock(mutex_); 
     // memory_order_relaxed is enough because storing to 
     // `is_value_set_` happens only in `SetIfEmpty` methods 
     // which are protected by mutex. 
     if (!is_value_set_.load(std::memory_order_relaxed)) { 
      new(GetPtr()) T(value); 
      is_value_set_.store(true, std::memory_order_release); 
     } 
    } 

    void SetIfEmpty(T&& value) { 
     std::lock_guard<std::mutex> lock(mutex_); 
     if (!is_value_set_.load(std::memory_order_relaxed)) { 
      new(GetPtr()) T(std::move(value)); 
      is_value_set_.store(true, std::memory_order_release); 
     } 
    } 

    //! This method might be safely call only if previous `IsEmpty()` 
    //! call returned `false`. 
    const T& Get() const { 
     assert(!IsEmpty()); 
     return *GetPtr(); 
    } 

    bool IsEmpty() const { 
     // memory_order_acquire loading to become synchronize with 
     // memory_order_release storing in `SetIfEmpty` methods. 
     return !is_value_set_.load(std::memory_order_acquire); 
    } 

    ~ThreadSafeHolder() { 
     if (!IsEmpty()) { 
      GetPtr()->~T(); 
     } 
    } 

private: 
    T* GetPtr() { 
     return reinterpret_cast<T*>(value_place_holder_); 
    } 

    const T* GetPtr() const { 
     return reinterpret_cast<const T*>(value_place_holder_); 
    } 

    // Reserved place for user data. 
    char value_place_holder_[sizeof(T)]; 
    // Mutex for protecting writing access to placeholder. 
    std::mutex mutex_; 
    // Boolean indicator whether value was set or not. 
    std::atomic<bool> is_value_set_; 
}; 

问题

  • 是代码一般正确的吗?
  • 访问is_value_set_成员是否正确同步?
  • 可能访问is_value_set_会员更轻松?

应用

我想开发这种支架从工作线程活跃异常传递到主线程。

主线程:

ThreadSafeHolder<std::exception_ptr> exceptionPtrHolder; 
// Run many workers. 
// Join workers. 
if (!exceptionPtrHolder.IsEmpty()) { 
    std::rethrow_exception(exceptionPtrHolder.Get()); 
} 

工作线程:

try { 
    while (exceptionPtrHolder.IsEmpty()) { 
     // Do hard work... 
    } 
} catch (...) { 
    exceptionPtrHolder.SetIfEmpty(std::current_exception()); 
} 

备注std::promise

std::promise不适合在这里(尽管std::promise::set_value是线程安全的),因为

如果没有共享状态或共享状态已存储值或异常,则会引发异常。

+0

您可能想要使用['std :: aligned_storage_t '](http://en.cppreference。com/w/cpp/types/aligned_storage)而不是你的char数组 – melak47

+1

另外,这个问题可能更适合https://codereview.stackexchange.com/ – melak47

+0

哦,我不小心把它贴在这里,谢谢你指点我。 关于codereview的这个问题[https://codereview.stackexchange.com/questions/105742/thread-safe-holder](https://codereview.stackexchange.com/questions/105742/thread-safe-holder)。 – user2932661

回答

0

不,这段代码不正确:T::~T()可能会被多次调用。可能你应该使用shared_ptr

你是什么意思在有效例外?抛出异常后工作线程是否继续执行?如何

我的意思是

  • 如果一个异常被处理则没有理由将其转发到另一个线程,它也已经被处理。
  • 其他工作线程应该解除异常转发,并且很可能由主线程重新启动,并且std::promise似乎对于此目的而言不算太坏。

那么,如何重新设置工作线程中的另一个异常,以及为了什么呢?

+0

我没有得到你。 'T ::〜T()'只在'ThreadSafeStorage ::〜ThreadSafeHolder()'中调用,因为只使用了一个'ThreadSafeStorage'对象,所以只调用一次。 我想在* many *工作线程中运行一些函数。该函数可能会抛出,如果它抛出一个工作者,我想停止其他工作线程并处理主线程中的异常。 – user2932661

+0

而'ThreadSafeStorage ::〜ThreadSafeHolder()'可能被多次调用。通常,它的名称是_copy_。如果是这样,为什么'std :: promise'和'std :: fututre'将主结果或异常转发到主线程是不好的?在工作线程之间,你可以使用'std :: shared_ptr '并检查它。 – Nevermore

+0

对不起,不是'std :: shared_ptr ',而是'std :: shared_ptr '。因此,由于异常被设置(意味着执行被中断并且异常被转发到主线程),所以没有办法设置另一个异常并且它是可以的,只需退出线程并加入即可。 – Nevermore