42

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?

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
Walter
  • 44,150
  • 20
  • 113
  • 196
  • `vector` only needs objects to be movable, and for `resize` they need to be no-throw-move-constructible. – Kerrek SB May 09 '13 at 15:42
  • @KerrekSB `std::mutex` has no other than the default constructor. – Walter May 09 '13 at 15:44
  • 3
    Instead of a `collection` and `collection` to protect the former, could you perhaps use a `collection>`? – Jerry Coffin May 09 '13 at 16:14
  • 2
    I feel like there is probably a better approach than what you're trying to do. Can you provide some context as to the underlying problem which requires mutexes in the first place? How are you writing into the objects, and how were you planning to use the mutexes? Offhand I'd say you could just make your objects thread-safe by adding a mutex as a member field of the class. – BTownTKD May 09 '13 at 18:15
  • @BTownTKD sorry, but this is going to be a bit lengthy ... so I refrain from giving details. However, I'm pretty sure that solution with mutexes is the best one. In face, atomic is not a good idea, as that would be implemented with much finer-grained mutexes by the library. – Walter May 10 '13 at 14:23
  • Note that the mutexes in a vector are allocated on the heap as well! And you can perfectly do this: std::vector vec(10); And also this: auto vec = std::vector;(10); – mfnx Apr 01 '19 at 11:34
  • @MFnx just to be pedantic, the word is *mutices*. – Walter Apr 05 '19 at 05:39

7 Answers7

23

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.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    What about `std::unique_ptr>`? On construction, the vector will use the default constructor of its elements, so not problem. – Walter May 09 '13 at 16:08
  • I also thought about `std::deque`. Why would I need to use `emplace()`? Wouldn't the constructor (of the deque) simply use the default constructor of its elements? – Walter May 09 '13 at 16:09
  • @Walter: Indeed, using the constructor is fine. I meant that you'd have to use `emplace()` rather than `push_back()` to add individual mutexes, but I didn't say that clearly enough. – Mike Seymour May 09 '13 at 16:12
  • 1
    @Walter: You can't use a vector if you want to resize it; wrapping it in a `unique_ptr` won't make any difference. If you have a *fixed* number, then a suitably constructed vector should work; but you say "potentially varying", which rules out using `vector`. – Mike Seymour May 09 '13 at 16:14
  • I didn't mean to `resize()` but instead to delete and re-construct the whole `vector` of mutexes. This should be as fast as `resize()` would have been. In this case, I think using a `unique_ptr` to hold the `vector` is the cleanest way, though perhaps one could destruct and re-construct in place (?). – Walter May 09 '13 at 18:09
  • 1
    @Walter: OK, I see what you mean now. That should work, but make sure you never do that while any mutex is locked. – Mike Seymour May 09 '13 at 18:27
  • Can you share an example of using emplace to store a std::mutex to a std::map? I was trying to use "std::map map_mutex; map_mutex.emplace("mystring",std::mutex()); // or map_mutex.emplace(std::make_pair("mystring",std::mutex()))". However, this doesn't seem to work. – Sanket_Diwale Aug 19 '22 at 21:12
21

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);
Awwit
  • 320
  • 2
  • 5
  • 3
    This answer confirms that `vector` doe not require move-constructible items. It depends on what you do with the `vector`. You can grow a `vector` in one way (*only* one way, your `swap` trick), but you can easily shrink such a vector and you can (as in your answer) ensure it has the right size to begin with. – Aaron McDaid Aug 19 '15 at 20:55
  • I fail to see why this answer isn't the preferred? How is this functionally different then .resize() other than the larger overhead. what do you mean by it can only grow in one way? (sorry if this isn't the correct format, I'm new to the forum) – Supamee Mar 27 '17 at 14:25
  • 4
    After messing around with it I now see why; if you use .swap() to grow the vector, all the elements are new. – Supamee Mar 27 '17 at 19:23
  • I wonder if this is an implementation quirk, or this should work on every conformant standard C++ library. – lvella Oct 11 '20 at 18:00
18

You could use std::unique_ptr<std::mutex> instead of std::mutex. unique_ptrs are movable.

Matthias Benkard
  • 15,497
  • 4
  • 39
  • 47
  • 2
    and create each mutex individually on the heap? are you kidding? – Walter May 09 '13 at 15:55
  • 10
    @Walter: It's a perfectly sensible suggestion, if the extra memory footprint and the cost of the extra level of indirection are acceptable. – Mike Seymour May 09 '13 at 16:01
  • 1
    @MikeSeymour and the cost of allocating & de-allocating many individual mutexes. Well this is a horrible workaround. Really, I don't want to copy mutexes at all. All I want are default constructed mutexes in a number that I can alter. Unfortunately, vector::resize() cannot do that ... – Walter May 09 '13 at 16:04
  • 2
    @Walter: Why don't you create a class that keeps the mutex associated with its object? Then write custom copy- and move-constructors, and assignment-operators, that allow your class to be used in containers just fine. Keeping bare mutex's around seems a bit silly. – GManNickG May 09 '13 at 16:13
  • what about `std::unique_ptr`, which is initialised to an appropriate size (`new std::mutex[N]`) and if that proves to be too small at later times, simply re-allocated? To me this looks like the best solution. – Walter May 10 '13 at 14:27
  • 1
    @Walter In that case, you will need to recreate all mutexes on resize. You can only safely do so when no existing mutex is currently held. Can you ensure that? – Matthias Benkard May 10 '13 at 18:13
  • _"Well this is a horrible workaround."_ Actually, this may be even a favorable workaround since it may avoid _false sharing_ (which you may have with a deque-based alternative). – Daniel Langr Mar 08 '23 at 12:39
9

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.

Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • interesting idea. It's not guaranteed that there are no two objectPtr resulting in the same mutex, causing more contention. I presume that this is okay so long as the number of mutexes is larger (by how much) than the number of threads. – Walter May 09 '13 at 18:13
  • Correct, you would experience collisions. If those collisions don't affect you this may be a more efficient alternative. – Captain Obvlious May 09 '13 at 18:59
  • I already used many fewer mutexes than actual ocbjects and groups the objects into chunks, because whenever a thread has to write into one objects, it is likely to also write into a nearby one. – Walter May 10 '13 at 14:25
  • This is a brilliant idea! It applies to my case in which collision would have minimal impact (just a small wait while a smart pointer contained in a big vector is either copied, updated or reset) – Antonio Feb 27 '23 at 11:16
3

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

schoppenhauer
  • 411
  • 3
  • 11
1

I'll sometimes use a solution along the lines of your 2nd option when I want a std::vector of classes or structs 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.

Matthew M.
  • 392
  • 2
  • 11
-4

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];
cyw
  • 163
  • 3
  • 11