问题:代码库有大量的数据结构,可以在使用>= 1编写器的线程之间访问。由于大量互斥锁,应用程序逻辑变得模糊。解决方案:创建一个模板类,通过成员函数提供同步访问。
这似乎很麻烦--这是徒劳的努力/反模式吗?
#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注释的
#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_发布于 2018-08-25 00:53:05
似乎是个不错的实现。你应该知道在这方面没有什么是万无一失的。“破坏”代码的两种方法是:
auto si = make_synchronized(42);
// ...
int *one;
si.alter([&](int& val) {
one = &val;
});
*one = 42; // use outside of the lock和
auto si = make_synchronized(42);
auto& si2 = si;
// ...
si.use([&](const int&) {
si2.alter([&](int&) {
// deadlock
});
});但这没什么大不了的,只要你相信你的用户(这可能意味着你自己!)以避免陷入这种情况。上面的第二种情况(死锁)特别容易隐藏,方法是将第二个si.alter放在助手函数中,或者混叠我使用si2的方式。
#undef DISALLOW_EVIL_CONSTRUCTORS
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)这里至少有三个问题。首先,您应该使用=delete而不是这个宏。简单地说:
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)成语的信息。
第三,你在用
#undef X
#define X Y作为一种表达方式,“我真的很希望X的意思是Y,即使其他人已经将X定义为不同的意思。”那可能是个坏主意。如果以前没有其他人定义过X,那么您的#undef是多余的(应该删除)。如果以前有人已经定义了X,那么您的#undef会使您自己的代码工作,但可能会破坏该人使用X的任何功能!与其以一种可能会破坏代码的方式重新定义X,不如在这种情况下只在这里出错(同样,解决方案是删除#undef)。
我注意到final关键字在class synchronized上。为什么这个类是final对您来说很重要?
template <typename Accessor>
void use(Accessor&& access) const {
read_lock l(mutex_);
std::forward<Accessor>(access)(value_);
}完美的转发在这里是很好的,但几乎可以肯定过度。我想不出任何情况,我希望访问者本身不是非忏悔者,也不是一个值。访问器应该始终是表单[&](const auto& value) { ... };它不需要有任何可能受到mutable影响的捕获,因此它永远不需要被调用为非const。这样我们就可以简化:
template<class Accessor>
void use(const Accessor& access) const {
read_lock l(mutex_);
access(value_);
}(顺便说一句,除了四个空格外,其他任何东西都是可怕的。:)
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_pair和make_tuple也使用parens,不过我不确定在这两种情况下是否可以观察到这种差异。)https://codereview.stackexchange.com/questions/202440
复制相似问题