Reputation: 45454
I have a large, but potentially varying, number of objects which are concurrently written into. I want to protect that access with mutexes. To that end, I thought I use a std::vector<std::mutex>
, but this doesn't work, since std::mutex
has no copy or move constructor, while std::vector::resize()
requires that.
What is the recommended solution to this conundrum?
edit: Do all C++ random-access containers require copy or move constructors for re-sizing? Would std::deque help?
edit again
First, thanks for all your thoughts. I'm not interested in solutions that avoid mutices and/or move them into the objects (I refrain from giving details/reasons). So given the problem that I want a adjustable number of mutices (where the adjustment is guaranteed to occur when no mutex is locked), then there appear to be several solutions.
1 I could use a fixed number of mutices and use a hash-function to map from objects to mutices (as in Captain Oblivous's answer). This will result in collisions, but the number of collisions should be small if the number of mutices is much larger than the number of threads, but still smaller than the number of objects.
2 I could define a wrapper class (as in ComicSansMS's answer), e.g.
struct mutex_wrapper : std::mutex
{
mutex_wrapper() = default;
mutex_wrapper(mutex_wrapper const&) noexcept : std::mutex() {}
bool operator==(mutex_wrapper const&other) noexcept { return this==&other; }
};
and use a std::vector<mutex_wrapper>
.
3 I could use std::unique_ptr<std::mutex>
to manage individual mutexes (as in Matthias's answer). The problem with this approach is that each mutex is individually allocated and de-allocated on the heap. Therefore, I prefer
4 std::unique_ptr<std::mutex[]> mutices( new std::mutex[n_mutex] );
when a certain number n_mutex
of mutices is allocated initially. Should this number later be found insufficient, I simply
if(need_mutex > n_mutex) {
mutices.reset( new std::mutex[need_mutex] );
n_mutex = need_mutex;
}
So which of these (1,2,4) should I use?
Upvotes: 44
Views: 40529
Reputation: 412
I'll sometimes use a solution along the lines of your 2nd option when I want a std::vector
of class
es or struct
s that each have their own std::mutex
. Of course, it is a bit tedious as I write my own copy/move/assignment operators.
struct MyStruct {
MyStruct() : value1(0), value2(0) {}
MyStruct(const MyStruct& other) {
std::lock_guard<std::mutex> l(other.mutex);
value1 = other.value1;
value2 = other.value2;
}
MyStruct(MyStruct&& other) {
std::lock_guard<std::mutex> l(other.mutex);
value1 = std::exchange(other.value1, 0);
value2 = std::exchange(other.value2, 0);
}
MyStruct& operator=(MyStruct&& other) {
std::lock_guard<std::mutex> l1(this->mutex), l2(other.mutex);
std::swap(value1, other.value1);
std::swap(value2, other.value2);
return *this;
}
MyStruct& operator=(const MyStruct& other) {
// you get the idea
}
int value1;
double value2;
mutable std::mutex mutex;
};
You don't need to "move" the std::mutex
. You just need to hold a lock on it while you "move" everything else.
Upvotes: 1
Reputation: 163
How about declaring each mutex as a pointer?
std::vector<std::mutex *> my_mutexes(10)
//Initialize mutexes
for(int i=0;i<10;++i) my_mutexes[i] = new std::mutex();
//Release mutexes
for(int i=0;i<10;++i) delete my_mutexes[i];
Upvotes: -4
Reputation: 342
If you want to create a certain length:
std::vector<std::mutex> mutexes;
...
size_t count = 4;
std::vector<std::mutex> list(count);
mutexes.swap(list);
Upvotes: 23
Reputation: 421
If efficiency is such a problem, I assume that you have only very small data structures which are changed very often. It is then probably better to use Atomic Compare And Swap (and other atomic operations) instead of using mutexes, specifically std::atomic_compare_exchange_strong
Upvotes: 3
Reputation: 20073
I suggest using a fixed mutex pool. Keep a fixed array of std::mutex
and select which one to lock based on the address of the object like you might do with a hash table.
std::array<std::mutex, 32> mutexes;
std::mutex &m = mutexes[hashof(objectPtr) % mutexes.size()];
m.lock();
The hashof
function could be something simple that shifts the pointer value over a few bits. This way you only have to initialize the mutexes once and you avoid the copy of resizing the vector.
Upvotes: 10
Reputation: 254691
vector
requires that the values are movable, in order to maintain a contiguous array of values as it grows. You could create a vector containing mutexes, but you couldn't do anything that might need to resize it.
Other containers don't have that requirement; either deque
or [forward_]list
should work, as long as you construct the mutexes in place either during construction, or by using emplace()
or resize()
. Functions such as insert()
and push_back()
will not work.
Alternatively, you could add an extra level of indirection and store unique_ptr
; but your comment in another answer indicates that you believe the extra cost of dynamic allocation to be unacceptable.
Upvotes: 25
Reputation: 15769
You could use std::unique_ptr<std::mutex>
instead of std::mutex
. unique_ptr
s are movable.
Upvotes: 18