Accessing std::vector
elements in a multithreaded environment requires careful consideration to avoid data races and ensure thread safety. Here are some strategies and best practices:
The most common approach is to use a mutex to synchronize access to the vector:
#include <iostream>
#include <vector>
#include <mutex>
#include <thread>
class ThreadSafeVector {
private:
std::vector<int> vec;
mutable std::mutex mutex;
public:
void push_back(int value){
std::lock_guard<std::mutex> lock(mutex);
vec.push_back(value);
}
int at(size_t index) const{
std::lock_guard<std::mutex> lock(mutex);
return vec.at(index);
}
size_t size() const{
std::lock_guard<std::mutex> lock(mutex);
return vec.size();
}
};
void writer(ThreadSafeVector& tsv){
for (int i = 0; i < 1000; ++i) {
tsv.push_back(i);
}
}
void reader(const ThreadSafeVector& tsv){
for (int i = 0; i < 1000; ++i) {
if (i < tsv.size()) {
std::cout << tsv.at(i) << " ";
}
}
}
int main(){
ThreadSafeVector tsv;
std::thread t1(writer, std::ref(tsv));
std::thread t2(reader, std::ref(tsv));
t1.join();
t2.join();
}
1 2 3 4 ...
In this example, all accesses to the vector are protected by a mutex, ensuring that only one thread can access the vector at a time.
If you have many readers and few writers, consider using a read-write lock (std::shared_mutex
in C++17):
#include <shared_mutex>
#include <vector>
class ThreadSafeVector {
private:
std::vector<int> vec;
mutable std::shared_mutex mutex;
public:
void push_back(int value){
std::unique_lock<std::shared_mutex> lock(
mutex);
vec.push_back(value);
}
int at(size_t index) const{
std::shared_lock<std::shared_mutex> lock(
mutex);
return vec.at(index);
}
};
This allows multiple threads to read simultaneously, but ensures exclusive access for writing.
For read-heavy scenarios, you might consider a copy-on-write approach:
#include <memory>
#include <mutex>
#include <vector>
class ThreadSafeVector {
private:
std::shared_ptr<std::vector<int>> vec;
mutable std::mutex mutex;
public:
ThreadSafeVector()
: vec(
std::make_shared<std::vector<int>>()){}
void push_back(int value){
std::lock_guard<std::mutex> lock(mutex);
if (vec.use_count() > 0) {
vec = std::make_shared<std::vector<
int>>(*vec);
}
vec->push_back(value);
}
std::vector<int> get_copy() const{
std::lock_guard<std::mutex> lock(mutex);
return *vec;
}
};
This approach creates a new copy of the vector only when a write operation occurs and there are other references to the current vector.
size()
and then accessing an element isn't atomic without proper synchronization.tbb::concurrent_vector
from Intel's Threading Building Blocks library, if you need high-performance concurrent access.Remember, thread safety often comes at the cost of performance. Always profile your application to ensure that your thread-safety measures aren't causing unacceptable performance bottlenecks.
Answers to questions are automatically generated and may not have been reviewed.
std::vector
Explore the fundamentals of dynamic arrays with an introduction to std::vector