Original Post
I've been trying to learn about multithreaded programming and I came to an interesting problem. How would you make a thread safe array? Similar to the standard library's vector. I tried wrapping it in locks like this:
[source lang="cpp"]template
class SafeArray : public IObject
{
public:
void pushBack(const T& obj)
{
m_lock.lock();
m_memory.push_back(obj);
m_lock.unlock();
}
void pushFront(const T& obj)
{
m_lock.lock();
m_memory.push_front(obj);
m_lock.unlock();
}
T popBack()
{
m_lock.lock();
T ret = m_memory.back();
m_memory.pop_back();
m_lock.unlock();
return ret;
}
///@brief This function attempts to pop an element from the back of the array. It returns false if the element couldn't be popped, and true otherwise.
bool tryPop(T& out)
{
if ( m_lock.tryLock() )
{
out = m_memory.back();
m_memory.pop_back();
m_lock.unlock();
return true;
}
return false;
}
///@brief Element access by index.
T operator[](int idx)
{
m_lock.lock();//Might be able to get rid of these locks
T ret = m_memory[idx];
m_lock.unlock();
return ret;
}
///@brief Attempts to access the object at the index.
///@param idx The index of the object you want to access
///@param obj A pointer to the object you wanted to access
///@return Returns false if the array was locked and could not be accessed
///in this case out will be set to NULL. True if the memory could be accessed.
bool tryAccess(int idx, T* out)
{
out = NULL;
if ( m_lock.tryLock() )
{
out = m_memory[idx];
m_lock.unlock();
return true;
}
return false;
}
///@brief Returns the size of the array.
///Don't use this to iterate over the array if elements might have been removed during iterating.
unsigned int size()
{
m_lock.lock();
unsigned int ret = m_memory.size();
m_lock.unlock();
return ret;
}
///@brief Deletes all elements in the array.
void clear()
{
m_lock.lock();
m_memory.clear();
m_lock.unlock();
}
///@brief Erases one element from the array at the given index.
void erase(int idx)
{
m_lock.lock();
m_memory.erase(m_memory.begin+idx);
m_lock.unlock();
}
///@return True if the array contains the value passed in. False Otherwise.
bool contains(T val)
{
bool ret=false;
m_lock.lock();
for (unsigned int i=0; i {
if ( m_memory == val )
{
ret = true;
break;
}
}
m_lock.unlock();
return ret;
}
///@brief Searches through the array and removes the passed value.
///@param val The value to search the array for and remove.
///@return True if succesfull. False otherwise.
bool remove(T val)
{
bool ret=false;
m_lock.lock();
std::vector::iterator i;
for (i = m_memory.begin(); i!=m_memory.end(); ++i)
{
if ( *i == val )
{
ret = true;
m_memory.erase(i);
break;
}
}
m_lock.unlock();
return ret;
}
private:
ThreadLock m_lock;
std::vector m_memory;
};[/source]
But obviously that has major problems. If an object is added to the array, or removed from the array while a thread is iterating over it then it could end up causing problems. I know about thread safe queues and how they work (mostly). But if you have objects stored in an array that can't just be popped off of the queue every time you use them, what do you do? I did read something about an array that worked by keeping it's own internal array that threads just copied off of so that they could get kind of a snapshot of the array's contents at the time they copied, and the array could still be updated during other threads iterating.
[source lang="cpp"]template
class SafeArray : public IObject
{
public:
void pushBack(const T& obj)
{
m_lock.lock();
m_memory.push_back(obj);
m_lock.unlock();
}
void pushFront(const T& obj)
{
m_lock.lock();
m_memory.push_front(obj);
m_lock.unlock();
}
T popBack()
{
m_lock.lock();
T ret = m_memory.back();
m_memory.pop_back();
m_lock.unlock();
return ret;
}
///@brief This function attempts to pop an element from the back of the array. It returns false if the element couldn't be popped, and true otherwise.
bool tryPop(T& out)
{
if ( m_lock.tryLock() )
{
out = m_memory.back();
m_memory.pop_back();
m_lock.unlock();
return true;
}
return false;
}
///@brief Element access by index.
T operator[](int idx)
{
m_lock.lock();//Might be able to get rid of these locks
T ret = m_memory[idx];
m_lock.unlock();
return ret;
}
///@brief Attempts to access the object at the index.
///@param idx The index of the object you want to access
///@param obj A pointer to the object you wanted to access
///@return Returns false if the array was locked and could not be accessed
///in this case out will be set to NULL. True if the memory could be accessed.
bool tryAccess(int idx, T* out)
{
out = NULL;
if ( m_lock.tryLock() )
{
out = m_memory[idx];
m_lock.unlock();
return true;
}
return false;
}
///@brief Returns the size of the array.
///Don't use this to iterate over the array if elements might have been removed during iterating.
unsigned int size()
{
m_lock.lock();
unsigned int ret = m_memory.size();
m_lock.unlock();
return ret;
}
///@brief Deletes all elements in the array.
void clear()
{
m_lock.lock();
m_memory.clear();
m_lock.unlock();
}
///@brief Erases one element from the array at the given index.
void erase(int idx)
{
m_lock.lock();
m_memory.erase(m_memory.begin+idx);
m_lock.unlock();
}
///@return True if the array contains the value passed in. False Otherwise.
bool contains(T val)
{
bool ret=false;
m_lock.lock();
for (unsigned int i=0; i
if ( m_memory == val )
{
ret = true;
break;
}
}
m_lock.unlock();
return ret;
}
///@brief Searches through the array and removes the passed value.
///@param val The value to search the array for and remove.
///@return True if succesfull. False otherwise.
bool remove(T val)
{
bool ret=false;
m_lock.lock();
std::vector
for (i = m_memory.begin(); i!=m_memory.end(); ++i)
{
if ( *i == val )
{
ret = true;
m_memory.erase(i);
break;
}
}
m_lock.unlock();
return ret;
}
private:
ThreadLock m_lock;
std::vector
};[/source]
But obviously that has major problems. If an object is added to the array, or removed from the array while a thread is iterating over it then it could end up causing problems. I know about thread safe queues and how they work (mostly). But if you have objects stored in an array that can't just be popped off of the queue every time you use them, what do you do? I did read something about an array that worked by keeping it's own internal array that threads just copied off of so that they could get kind of a snapshot of the array's contents at the time they copied, and the array could still be updated during other threads iterating.