假设我们有std::map
容器,我们希望在插入,擦除,搜索和编辑记录方面使其成为线程安全的。同时我们希望线程可以并行处理不同的记录(读取和编辑记录)。为此,我为记录编辑操作创建了一个单独的类,它使用互斥锁保护。
class Data
{
public:
Data(const std::string& data) : _mutex(), _data(data) { }
void setData(const std::string& data)
{
std::lock_guard<std::mutex> locker(_mutex);
_data = data;
}
const std::string& getData() const { return _data; }
private:
std::mutex _mutex;
std::string _data;
};
class Storage
{
public:
void insertData(size_t key, const std::string& data)
{
std::lock_guard<std::mutex> locker(_mutex);
_storage[key] = data;
}
void eraseData(size_t key)
{
std::lock_guard<std::mutex> locker(_mutex);
_storage.erase(key);
}
const std::string& getData(size_t key) const { return _storage[key].getData(); }
void setData(size_t key, const std::string& data) { _storage[key].setData(data); }
private:
std::mutex _mutex;
std::map<size_t, Data> _storage;
};
现在假设线程抓住&#34;本地&#34;要编辑的某些记录的互斥锁(Data::setData
方法调用)。在同一时间,其他线程抓住&#34;全球&#34; mutex删除此记录(Storage::eraseData
方法调用) - 有什么问题吗?此代码还有哪些其他问题?
答案 0 :(得分:3)
首先解决您的并发问题。这是一个C ++ 14解决方案,因为C ++ 11版本更冗长,而且我们没有我们想要的所有锁定原语:
template<class T>
struct thread_safe {
template<class F>
auto read( F&& f ) const {
std::shared_lock<decltype(mutex)> lock(mutex);
return std::forward<F>(f)(t);
}
template<class F>
auto write( F&& f ) {
std::unique_lock<decltype(mutex)> lock(mutex);
return std::forward<F>(f)(t);
}
template<class O>
thread_safe(O&&o):t(std::forward<O>(o)) {}
thread_safe() = default;
operator T() const {
return o.read([](T const& t){return t;});
}
// it is really this simple:
thread_safe( thread_safe const& o ):t( o ) {}
// forward to above thread safe copy ctor:
thread_safe( thread_safe & o ):thread_safe( const_cast<thread_safe const&>(o) ) {}
thread_safe( thread_safe && o ):thread_safe(o) {}
thread_safe( thread_safe const&& o ):thread_safe(o) {}
thead_safe& operator=( thread_safe const& o ) {
write( [&o](auto& target) {
target = o;
});
return *this;
}
template<class O>
thread_safe& operator=( O&& o ) {
write([&o](auto& t){ t = std::forward<O>(o); });
return *this;
}
private:
T t;
mutable std::shared_timed_mutex mutex;
};
这是围绕任意类的线程安全包装。
我们可以直接使用它:
typedef thread_safe< std::map< size_t, thread_safe<std::string> > > my_map;
这里我们有两级线程安全地图。
使用示例,将条目33设置为"hello"
:
my_map.write( [&](auto&& m){
m[33] = "hello";
} );
每个元素和地图上都有许多读者,单作者。从read
或write
电话中返回迭代器并不安全。
当然,您应该测试并审核上述代码。我没有。
核心理念非常简单。要阅读,您必须.read
线程安全对象。传入的lambda获取基础数据const&
。在std::
数据上,保证这些数据是多读者安全的。
要写,您必须.write
。这会获得一个独占锁定,阻止其他.read
。这里的lambda得到基础数据的&
。
我添加了operator T
和=
以及copy-construct以使类型更加规则。这样做的代价是你可能会意外地产生很多锁定/解锁行为。优点是m[33] = "hello"
正常工作,这很棒。
答案 1 :(得分:1)
你有两个大问题:
如果一个线程在另一个线程调用insertData
的同时调用getData
会发生什么?对operator[]
的调用可能会崩溃,因为地图在尝试访问时会被修改。
如果一个线程调用eraseData
而另一个线程仍在使用从getData
返回的引用,会发生什么?引用可能无效,导致崩溃。