我正在开发一个线程安全的std :: vector实现,以下是一个完整的初步尝试:
#ifndef THREADSAFEVECTOR_H
#define THREADSAFEVECTOR_H
#include <iostream>
#include <vector>
#include <mutex>
#include <cstdlib>
#include <memory>
#include <iterator>
#include <algorithm>
#include <initializer_list>
#include <functional>
template <class T, class Alloc=std::allocator<T>>
class ThreadSafeVector
{
private:
std::vector<T> threadSafeVector;
std::mutex vectorMutex;
public:
/*need to use typename here because std::allocator<T>::size_type, std::allocator<T>::value_type, std::vector<T>::iterator,
and std::vector<T>::const_reverse_iterator are 'dependent names' in that because we are working with a templated class,
these expressions may depend on types of type template parameters and values of non-template parameters*/
typedef typename std::vector<T>::size_type size_type;
typedef typename std::vector<T>::value_type value_type;
typedef typename std::vector<T>::iterator iterator;
typedef typename std::vector<T>::const_iterator const_iterator;
typedef typename std::vector<T>::reverse_iterator reverse_iterator;
typedef typename std::vector<T>::const_reverse_iterator const_reverse_iterator;
typedef typename std::vector<T>::reference reference;
typedef typename std::vector<T>::const_reference const_reference;
/*wrappers for three different at() functions*/
template <class InputIterator>
void assign(InputIterator first, InputIterator last)
{
//using a local lock_guard to lock mutex guarantees that the mutex will be unlocked on destruction and in the case of an exception being thrown
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.assign(first, last);
}
void assign(size_type n, const value_type& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.assign(n, val);
}
void assign(std::initializer_list<value_type> il)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.assign(il.begin(), il.end());
}
/*wrappers for at() functions*/
reference at(size_type n)
{
return threadSafeVector.at(n);
}
const_reference at(size_type n) const
{
return threadSafeVector.at(n);
}
/*wrappers for back() functions*/
reference back()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.back();
}
const reference back() const
{
return threadSafeVector.back();
}
/*wrappers for begin() functions*/
iterator begin()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.begin();
}
const iterator begin() const noexcept
{
return threadSafeVector.begin();
}
/*wrapper for capacity() fucntion*/
size_type capacity() const noexcept
{
return threadSafeVector.capacity();
}
/*wrapper for cbegin() function*/
const iterator cbegin()
{
return threadSafeVector.cbegin();
}
/*wrapper for cend() function*/
const iterator cend()
{
return threadSafeVector.cend();
}
/*wrapper for clear() function*/
void clear()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.clear();
}
/*wrapper for crbegin() function*/
const_reverse_iterator crbegin() const noexcept
{
return threadSafeVector.crbegin();
}
/*wrapper for crend() function*/
const_reverse_iterator crend() const noexcept
{
return threadSafeVector.crend();
}
/*wrappers for data() functions*/
value_type* data()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.data();
}
const value_type* data() const noexcept
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.data();
}
/*wrapper for emplace() function*/
template <class... Args>
void emplace(const iterator position, Args&&... args)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.emplace(position, args...);
}
/*wrapper for emplace_back() function*/
template <class... Args>
void emplace_back(Args&&... args)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.emplace_back(args...);
}
/*wrapper for empty() function*/
bool empty() const noexcept
{
return threadSafeVector.empty();
}
/*wrappers for end() functions*/
iterator end()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.end();
}
const iterator end() const noexcept
{
return threadSafeVector.end();
}
/*wrapper functions for erase()*/
iterator erase(const_iterator position)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.erase(position);
}
iterator erase(const_iterator first, const_iterator last)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.erase(first, last);
}
/*wrapper functions for front()*/
reference front()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.front();
}
const reference front() const
{
return threadSafeVector.front();
}
/*wrapper function for get_allocator()*/
value_type get_allocator() const noexcept
{
return threadSafeVector.get_allocator();
}
/*wrapper functions for insert*/
iterator insert(const_iterator position, const value_type& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.insert(position, val);
}
iterator insert(const_iterator position, size_type n, const value_type& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.insert(position, n, val);
}
template <class InputIterator>
iterator insert(const_iterator position, InputIterator first, InputIterator last)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.insert(position, first, last);
}
iterator insert(const_iterator position, value_type&& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.insert(position, val);
}
iterator insert(const_iterator position, std::initializer_list<value_type> il)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.insert(position, il.begin(), il.end());
}
/*wrapper function for max_size*/
size_type max_size() const noexcept
{
return threadSafeVector.max_size();
}
/*wrapper functions for operator =*/
std::vector<T>& operator= (const std::vector<T>& x)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.swap(x);
}
std::vector<T>& operator= (std::vector<T>&& x)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector=std::move(x);
}
std::vector<T>& operator= (std::initializer_list<value_type> il)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.assign(il.begin(), il.end());
return *this; //is this safe to do?
}
/*wrapper functions for operator []*/
reference operator[] (size_type n)
{
return std::ref(n);
}
const_reference operator[] (size_type n) const
{
return std::cref(n);
}
/*wrapper function for pop_back()*/
void pop_back()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.pop_back();
}
/*wrapper functions for push_back*/
void push_back(const value_type& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.push_back(val);
}
void push_back(value_type&& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.push_back(val);
}
/*wrapper functions for rbegin()*/
reverse_iterator rbegin() noexcept
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.rbegin();
}
const_reverse_iterator rbegin() const noexcept
{
return threadSafeVector.rbegin();
}
/*wrapper functions for rend()*/
reverse_iterator rend() noexcept
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
return threadSafeVector.rend();
}
const_reverse_iterator rend() const noexcept
{
return threadSafeVector.rend();
}
/*wrapper function for reserve()*/
void reserve(size_type n)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.reserve(n);
}
/*wrapper functions for resize()*/
void resize(size_type n)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.resize(n);
}
void resize(size_type n, const value_type& val)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.resize(n, val);
}
void shrink_to_fit()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.shrink_to_fit();
}
//add function for size
size_type size() const noexcept
{
return threadSafeVector.size();
}
/*wrapper function for swap()*/
void swap(std::vector<T>& x)
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
threadSafeVector.swap(x);
}
void print()
{
std::lock_guard<std::mutex> vectorLockGuard(vectorMutex);
for(const auto & element : threadSafeVector)
{
std::cout << element << std::endl;
}
std::cout << std::endl;
}
};
#endif
我的实现基于cplusplus.com上的vector类及其成员函数的描述以及the implementation of the vector class from the STL的帮助。现在,关于我到目前为止编写的代码,我有几个问题:
当返回迭代器时,我不确定是否应该锁定互斥锁然后返回迭代器,因为迭代器的有效性可能会由于多个线程试图访问它而发生变化,所以我继续为所有非const迭代器锁定互斥锁。这是正确的做法吗?
我的理解是,在处理多线程代码时,不应该从函数返回指针,因为这为用户代码提供了一个“后门”(缺少一个更好的术语)来执行一些可能有问题的活动。那么,对于assignment operator的实现,有没有另一种方法来编写这些函数,以便它不返回* this?
我选择使用lock_guard的所有本地实例,而不是将其作为私有数据成员。如果我有一个私人数据成员,那会更好吗?
非常感谢提前: - )
答案 0 :(得分:2)
线程之间的同步是一个全局问题;它无法在本地解决。所以正确的答案是解决问题。
这种方法只是错误的粒度级别。防止对成员函数的冲突同时调用不会使容器在任何有用的意义上都是线程安全的;用户仍然必须确保操作的序列是线程安全的,这意味着在进行一系列操作时保持锁定。
举一个简单的例子,考虑
void swap(vector<int>& v, int idx0, int idx1) {
int temp = v[idx0];
v[idx0] = v[idx1];
v[idx1] = temp;
}
现在,如果在将v[idx1]
复制到v[idx0]
之后,其他一些线程出现并删除了向量中的所有数据,会发生什么?对v[idx1]
的赋值写入随机存储器。这不是一件好事。为防止这种情况发生,用户代码必须确保贯穿执行swap
没有其他线程正在弄乱向量。 vector
的实施无法实现。
答案 1 :(得分:0)
如果您希望一致的实现没有太多的复杂性,则至少需要两个新的成员函数,例如disable_write()
和enable_write()
。
使用矢量的代码可以选择是否要在某些读取代码块期间保持一致状态。它将在读取块的开头调用disable_write()
,并在完成需要一致向量状态的块时调用enable_write()
。
添加另一个“write_lock”互斥锁,并在进行更改的每个成员函数中使用此互斥锁。
当写锁定处于活动状态时,应自由允许读取操作,因此对于只读取数据的成员函数,不需要“write_lock”互斥锁。你已经拥有的Mutex已经足够了。
另外,你可以添加另一个类来锁定你的向量,例如一些等价的lock_guard
,甚至让disable_write()
和enable_write()
私有和朋友用这个类,以防止意外写锁从未发布。
答案 2 :(得分:0)
向量似乎天生就不是线程安全的。这似乎很残酷,但是对我来说,同步向量的最简单方法是将其封装在另一个线程安全的对象中...
struct guardedvector {
std::mutex guard;
std::vector myvector;
}
guardedvector v;
v.guard.lock();
... use v.myvector
v.guard.unlock();
在win32中,您还可以使用薄型读写器锁(SRW)。它们是轻巧快速的互斥锁,可以与多个读取器/一个写入器一起使用。在这种情况下,您可以用一个srwlock来代替防护罩。调用者代码负责调用正确的锁定方法。最后,您可以对结构进行模板化并内联。
答案 3 :(得分:0)
与我自己讨论之后,使向量安全线程化的最佳方法是从向量继承并添加防护。
enter code here
template <class t> class guardedvector : public std::vector<t> {
std::mutex guard;
};
guardedvector v;
v.lock();
//...iterate, add, remove...
v.unlock();
您可以使用swrlock代替互斥锁。但是,如果您有很多读取向量的线程,那么可能会导致写入器线程出现延迟。在某些情况下,您可以仅使用原子操作并为编写者创建优先级模型来重新创建自定义线程模型,但这需要对处理器如何工作有深入的了解。为简单起见,您可以更改writer线程的优先级。