首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >同步模板包装类

同步模板包装类
EN

Code Review用户
提问于 2018-08-24 22:16:11
回答 1查看 420关注 0票数 2

问题:代码库有大量的数据结构,可以在使用>= 1编写器的线程之间访问。由于大量互斥锁,应用程序逻辑变得模糊。解决方案:创建一个模板类,通过成员函数提供同步访问。

这似乎很麻烦--这是徒劳的努力/反模式吗?

代码语言:javascript
复制
#ifndef SYNCHRONIZED_HPP_
#define SYNCHRONIZED_HPP_

#include <functional>
#include <mutex>
#include <shared_mutex>
#include <type_traits>
#include <utility>


#undef DISALLOW_EVIL_CONSTRUCTORS
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
  TypeName(const TypeName&);                 \
  void operator=(const TypeName&)


template <typename T>
class synchronized;

template <typename T> auto
make_synchronized(T&& value) {
  return synchronized<T>{ std::forward<T>(value) };
}

template <typename T>
class synchronized final {
 public:
  using value_t = std::remove_reference_t<T>;

  template <typename... Args>
  explicit synchronized(Args&&... args)
    : value_(std::forward<Args>(args)...)
  {}

  value_t get() const {
    read_lock l(mutex_);
    return value_;
  }
  
  template <typename U> void
  set(U&& new_value) {
    write_lock l(mutex_);
    value_ = std::forward<U>(new_value);
  }
  
  template <typename Accessor>
  void use(Accessor&& access) const {
    read_lock l(mutex_);
    std::forward<Accessor>(access)(value_);
  }

  template <typename Mutator>
  void alter(Mutator&& func) {
    write_lock l(mutex_);
    std::forward<Mutator>(func)(value_);
  }

 private:
  value_t value_;
  using mutex_t = std::shared_timed_mutex;
  using read_lock = std::shared_lock<mutex_t>;
  using write_lock = std::unique_lock<mutex_t>;
  mutable mutex_t mutex_{};

  DISALLOW_EVIL_CONSTRUCTORS(synchronized);
};


#endif // SYNCHRONIZED_HPP_

带有doxy注释的

(相同代码)

代码语言:javascript
复制
#ifndef SYNCHRONIZED_HPP_
#define SYNCHRONIZED_HPP_

#include <functional>
#include <mutex>
#include <shared_mutex>
#include <type_traits>
#include <utility>


#undef DISALLOW_EVIL_CONSTRUCTORS
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
  TypeName(const TypeName&);                 \
  void operator=(const TypeName&)





template <typename T>
class synchronized;


/**
* @brief
*    Make a synchronized<T> using template deduction.
*
* @sa synchronized
*/
template <typename T> auto
make_synchronized(T&& value) {
  return synchronized<T>{ std::forward<T>(value) };
}

/**
* @brief
*    Provides straightforward thread-synchronized access to template type.
*
* @note
*    While access to the immediate type is synchronized, this class does not
*  prevent non-synchronized access of pointer or reference members of the
*  template type.
*
* @note
*    For applicable types, prefer std::atomic<T>.
*/
template <typename T>
class synchronized final {
 public:
  using value_t = std::remove_reference_t<T>;

  template <typename... Args>
  explicit synchronized(Args&&... args)
    : value_(std::forward<Args>(args)...)
  {}

  /**
  * @brief
  *    Thread-sychronized get.
  */
  value_t get() const {
    read_lock l(mutex_);
    return value_;
  }

  /**
  * @brief
  *    Thread-sychronized set.
  */
  template <typename U> void
  set(U&& new_value) {
    write_lock l(mutex_);
    value_ = std::forward<U>(new_value);
  }

  /**
  * @brief
  *    Use the underlying value where get() would be less-trivial or
  *  otherwise unsuitable.
  *
  * @note
  *    Prefer get(), especially if this access takes a long time, as to not
  *  starve a writer thread or other reader threads.
  */
  template <typename Accessor>
  void use(Accessor&& access) const {
    read_lock l(mutex_);
    std::forward<Accessor>(access)(value_);
  }

  /**
  * @brief
  *    Alter (mutate) the underlying value where get() and set() would be
  *  less-trivial or otherwise unsuitable.
  *
  * @note
  *    Prefer the combination of get() then set(), especially if this access
  *  takes a long time, as to not starve other writer or reader threads.
  */
  template <typename Mutator>
  void alter(Mutator&& func) {
    write_lock l(mutex_);
    std::forward<Mutator>(func)(value_);
  }

 private:
  value_t value_;
  using mutex_t = std::shared_timed_mutex;
  using read_lock = std::shared_lock<mutex_t>;
  using write_lock = std::unique_lock<mutex_t>;
  mutable mutex_t mutex_{};

  DISALLOW_EVIL_CONSTRUCTORS(synchronized);
};


#endif // SYNCHRONIZED_HPP_
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-08-25 00:53:05

似乎是个不错的实现。你应该知道在这方面没有什么是万无一失的。“破坏”代码的两种方法是:

代码语言:javascript
复制
auto si = make_synchronized(42);
// ...
int *one;
si.alter([&](int& val) {
    one = &val;
});
*one = 42;  // use outside of the lock

代码语言:javascript
复制
auto si = make_synchronized(42);
auto& si2 = si;
// ...
si.use([&](const int&) {
    si2.alter([&](int&) {
        // deadlock
    });
});

但这没什么大不了的,只要你相信你的用户(这可能意味着你自己!)以避免陷入这种情况。上面的第二种情况(死锁)特别容易隐藏,方法是将第二个si.alter放在助手函数中,或者混叠我使用si2的方式。

代码语言:javascript
复制
#undef DISALLOW_EVIL_CONSTRUCTORS
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
  TypeName(const TypeName&);                 \
  void operator=(const TypeName&)

这里至少有三个问题。首先,您应该使用=delete而不是这个宏。简单地说:

代码语言:javascript
复制
template <typename T>
class synchronized final {
public:
    synchronized(synchronized&&) = delete;
    synchronized(const synchronized&) = delete;
    synchronized& operator=(synchronized&&) = delete;
    synchronized& operator=(const synchronized&) = delete;

(一旦您显式地=deleted移动操作,进一步=delete复制操作是不必要的.但为了清晰起见,建议如此。)

第二,虽然在这种特殊情况下不太适用,但是拥有一个由多个语句或声明组成的宏通常是一个危险的标志。查询“卫生宏在C”,以获得更多关于do { ... } while (0)成语的信息。

第三,你在用

代码语言:javascript
复制
#undef X
#define X Y

作为一种表达方式,“我真的很希望X的意思是Y,即使其他人已经将X定义为不同的意思。”那可能是个坏主意。如果以前没有其他人定义过X,那么您的#undef是多余的(应该删除)。如果以前有人已经定义了X,那么您的#undef会使您自己的代码工作,但可能会破坏该人使用X的任何功能!与其以一种可能会破坏代码的方式重新定义X,不如在这种情况下只在这里出错(同样,解决方案是删除#undef)。

我注意到final关键字在class synchronized上。为什么这个类是final对您来说很重要?

代码语言:javascript
复制
template <typename Accessor>
void use(Accessor&& access) const {
  read_lock l(mutex_);
  std::forward<Accessor>(access)(value_);
}

完美的转发在这里是很好的,但几乎可以肯定过度。我想不出任何情况,我希望访问者本身不是非忏悔者,也不是一个值。访问器应该始终是表单[&](const auto& value) { ... };它不需要有任何可能受到mutable影响的捕获,因此它永远不需要被调用为非const。这样我们就可以简化:

代码语言:javascript
复制
template<class Accessor>
void use(const Accessor& access) const {
    read_lock l(mutex_);
    access(value_);
}

(顺便说一句,除了四个空格外,其他任何东西都是可怕的。:)

代码语言:javascript
复制
template <typename T> auto
make_synchronized(T&& value) {
  return synchronized<T>{ std::forward<T>(value) };
}

看起来不错但是..。

  • 这里的auto没有为您购买任何东西;最好将返回类型显式地指定为synchronized<T>
  • synchronized<T>{ ... }中的大括号与标准库中其他地方的make_函数的工作方式不一致。make_shared使用parens。make_unique使用parens。要观察差异,请创建一个类A,其中A{...}A(...)调用不同的构造函数像这样。(make_pairmake_tuple也使用parens,不过我不确定在这两种情况下是否可以观察到这种差异。)
票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/202440

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档