How can I use something like stdvectorstdmutex?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Using one mutex per element is a common way to implement fine-grained locking in C++. The catch is that std::mutex is neither copyable nor movable, so containers that rely on relocating elements can be awkward to combine with it.
What Works with std::vector<std::mutex>
A fixed-size std::vector<std::mutex> can work if you know the final number of mutexes up front and construct the vector at that size immediately.
This compiles because each mutex is default-constructed directly in place. No copies or moves are needed for the initial construction.
Why Growth Is the Problem
The trouble starts when the vector needs to grow or reallocate. A vector normally moves or copies existing elements into new storage, but a std::mutex cannot be copied or moved. That means operations such as push_back, reserve, or growing resize can fail to compile.
So the practical rule is:
- fixed size from the start is fine
- dynamic growth is not a good fit for
std::vector<std::mutex>
Good Alternatives for Dynamic Collections
If the number of locks must grow dynamically, use a container that does not relocate existing elements the same way, or store indirection objects instead of mutexes directly.
One common option is std::deque<std::mutex>:
Another option is a vector of owning pointers:
Pointers add a layer of indirection, but they let the vector move pointer objects while each mutex stays at a stable address on the heap.
Often the Better Design Is to Lock the Object, Not the Index
Instead of keeping data in one container and locks in another, it is often cleaner to bundle the mutex with the protected data.
That design makes it harder for the data and lock arrays to get out of sync. It also makes the ownership model easier to understand when the code is maintained later.
Use RAII for Locking
Whatever container you choose, prefer std::lock_guard or std::scoped_lock over manual lock() and unlock() calls. RAII avoids forgotten unlocks when exceptions or early returns happen.
That is especially important with many locks, where manual bookkeeping becomes fragile very quickly.
Common Pitfalls
The biggest pitfall is assuming that if std::vector<std::mutex> locks(4); compiles, then push_back and resize will behave the same way. They do not. Growth-related operations can require moving elements, which std::mutex does not support.
Another issue is maintaining separate vectors for data and locks. If one vector changes size or indexing rules independently, the locking logic becomes error-prone.
Also watch out for over-locking. One mutex per element increases concurrency, but it also increases complexity and can hurt performance if the protected work is tiny.
If you later need to lock multiple elements at once, design the lock ordering carefully. Fine-grained locking helps throughput, but it also makes deadlocks easier to introduce by accident.
Summary
- '
std::vector<std::mutex>works when the size is fixed at construction time.' - Dynamic growth is problematic because
std::mutexcannot be copied or moved during reallocation. - Use
std::deque<std::mutex>orstd::vector<std::unique_ptr<std::mutex>>when you need a growing collection. - Bundling each mutex with the data it protects is often a cleaner design.
- Use RAII wrappers such as
std::lock_guardto manage locking safely.

