How can I use something like std::vector std::mutex ?
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.
How can I use std::vector safely with mutex?
You are ignoring the result of try_lock
, so you aren't meaningfully using a mutex. Your code has undefined behaviour because of a data race.
If you never want to block, use the result of try_lock
if (g_to_be_downloaded_tasks_mutex.try_lock()) {
g_to_be_downloaded_tasks.push_back(temp);
g_to_be_downloaded_tasks_mutex.unlock();
}
if(g_to_be_downloaded_tasks_mutex.try_lock() && (g_to_be_downloaded_tasks.size() > 0))
{
curr_task = g_to_be_downloaded_tasks.front();
g_to_be_downloaded_tasks_mutex.unlock();
}
More commonly, the producing side would wait for the lock, otherwise it is discarding work
{
std::lock_guard guard(g_to_be_downloaded_tasks_mutex);
g_to_be_downloaded_tasks.push_back(temp);
}
How to use shared_mutex for objects in std::vector elements
Based on the answers provided in the comments, I think my best option will be to find a more creative solution that sidesteps the issue entirely. In this case I will probably process ahead of time and write the results to a buffer. This way Rendering and Processing should by design avoid any conflicts, hence avoiding the need for lock conditions.
Providing I remember, this will most likely become the accepted solution (since the button can't be pressed for 2 days).
storing mutexes in a vector/deque c++
With a firm and low upper bound on n
you could reasonably do something like this:
#include <iostream>
#include <mutex>
#include <vector>
int
main()
{
constexpr unsigned n_max = 5;
unsigned n;
std::cout << "Enter n: ";
std::cin >> n;
if (std::cin.fail())
throw "oops";
if (n > n_max)
throw "oops";
std::vector<std::mutex> mutexes(n);
std::vector<std::unique_lock<std::mutex>> locks;
for (auto& m : mutexes)
locks.emplace_back(m, std::defer_lock);
switch (locks.size())
{
case 0:
break;
case 1:
locks.front().lock();
break;
case 2:
std::lock(locks[0], locks[1]);
break;
case 3:
std::lock(locks[0], locks[1], locks[2]);
break;
case 4:
std::lock(locks[0], locks[1], locks[2], locks[3]);
break;
case 5:
std::lock(locks[0], locks[1], locks[2], locks[3], locks[4]);
break;
default:
throw "oops";
}
}
It isn't that pretty. But it is easy to reason about and thus reliable.
Notes:
You need to use
std::lock(m1, m2, ...)
to reliably lock more than onemutex
, or re-invent an algorithm such asstd::lock
to avoid deadlock. One such alternative algorithm is if you can guarantee that everyone always locks the mutexes inmutexes
in the same order (say by index), then you don't needstd::lock
at all, just loop thru and lock `em.lock_guard
is problematic to put invector
one at a time asvector<T>::emplace_back
requiresT
to be move constructible. That is one of the reasons whyunique_lock
works here andlock_guard
doesn't.mutexes
gets away with holding non-movable mutexes because it constructs thevector
all at once instead of adding to it withemplace_back
.In this example
locks
holds references intomutexes
. Make sure you don't have lifetime issues between these two containers (mutexes
must outlivelocks
).If you need to add non-movable items to the end of a sequence, switch to
deque
, that will work wherevector
won't.Unlocking order doesn't matter, don't worry about it. Locking order matters only if different threads might lock in different orders. If all threads always lock in the same order, don't worry about it. But if all threads always lock in the same order, consider replacing the n mutexes with a single mutex, as that sounds equivalent.
The code above assumes that somehow different threads might be locking in a different order, and perhaps a subset of
mutexes
. And obviously it won't scale to largen
.
With Edit2 in the question, I believe this code to be viable. It will reliably work with different threads locking mutexes
in different orders. Each thread should form its own local copy of locks
and send that through the switch
. If a thread for some reason needs its locks
to be a subset of mutexes
, or to build it in a different order, no problem. That is what this solution is designed for.
Plug
If you are interested in the algorithm behind std::lock
, here are performance tests for a variety of potential implementations of it, including test code that you can run on your own platform:
Dining Philosophers Rebooted
If you find that your implementation of std::lock
is suboptimal, have a talk with your implementor. :-)
Need a resizeable container for std::mutex
From the linked page,
If you need to add non-movable items to the end of a sequence, switch to
deque
, that will work wherevector
won't.
If you need insertions or deletions that are neither at the beginning nor at the end, then of course std::deque
won't work either and you would need to use something like std::vector<std::unique_ptr<std::mutex>>
. This is better than using raw pointers because it guarantees that the mutexes will get released when they're removed from the vector or when the vector goes out of scope (except in the case of abnormal program termination).
How to vector::emplace_back a class that has a shared_mutex?
Mutexes are not safe to copy/move in general; it simply doesn't compile, and the action itself doesn't make sense.
If you have a type with a mutex, and you are copy/moving it, you are almost certainly doing something wrong.
A vector of a class with a lock is bad smell, don't do it.
The semantics start acting really, really strange.
If an object has a mutex, that mutex has an identity more so than it has a value. You don't have the value of that mutex locked, you have the identity of that mutex locked. Identities cannot be copied, so classes with mutexes in them either cannot be copied, or do mix-semantics copy/move operations where part of their state is copied/moved, and the rest isn't.
Mixed semantics types are bonkers, and are ridiculously hard to reason about. This is the same reason why one struct
shouldn't contain both a reference and a value, but much much worse.
std::vector
is designed for regular or semi-regular value types. When you emplace, it may allocate a new buffer and move the elements over to it.
Putting a type with a mutex in it means that regular or semi-regular semantics are incoherent on them; it cannot apply to its entire state.
It is possible to make it work, but it isn't a good plan. What you'd do is write your incoherent copy/move ctors and assignment operations that don't duplicate/move the mutex state, and do something to the rest of the state. There could possibly be some locking involved there, but even that gets nasty and tricky.
(Mutex based threading doesn't compose; now you have incoherent values guarded by multiple locks being copied around. It will end badly.)
(Incoherent here refers to the mixed semantics; part of the object (the mutex) isn't copied/moved, the value meanwhile is. Coherent semantics means "the class all follows the same semantics", incoherent means it doesn't have coherent semantics.)
The least insane option involves using std::lock
to lock the mutexes of the source/destination object before you do the assignment, and on construction don't bother locking the destination (just the source) because no other thread should have access to the object while it is being constructed.
I mean, I've done that.
It isn't a good plan.
A slightly less bad plan is to have a vector of unique pointers to your class. Here the identity of the objects isn't disposable; the vector holds unique_ptr
s (which are semi-regular, hence can sensibly be moved), which in turn point to objects with a stable identity.
A mutex in those is somewhat reasonable. Having a dynamic vector of such mutexes is often a bad sign (bad code smell), but it isn't complete insanity.
Using a C++ std::vector as a queue in a thread
I'm going to use std::atomic<T>::wait
which is a C++20
feature, there is a way to do it with condition variables too however, and they exist since C++11
.
Include <atomic>
and <mutex>
You will need a member atomic_bool.std::atomic_bool RequestPassed = false;
and a member mutexstd::mutex RequestHandleMutex;
Your handleRequest function would then become
void handleRequest(item) {
std::lock_guard<std::mutex> lg(RequestHandleMutex)
toProcess.push_back(item);
RequestPassed.store(true);
RequestPassed.notify_all();
}
and your loop would be this
while(true) {
RequestPassed.wait(false);
std::lock_guard<std::mutex> lg(RequestHandleMutex)
/* handle latest item passed */
RequestPassed.store(false);
}
This way, the while thread waits instead of constantly iterating (saving cpu power and battery). If you then use handleRequest
, the atomic_bool gets notified to stop waiting, the request is handled (mutex is locked so no new requests can come while this happens), RequestPassed is reset to false, and the thread waits for the next request.
Can I make the data of an entire C++ class be std::atomic
std::atomic
requires the type to be trivially copyable. Since you are saying std::vector
is involved, that makes it impossible to use it, either on the whole structure or the std::vector
itself.
The purpose of std::atomic
is to be able to atomically replace the whole value of the object. You cannot do something like access individual members or so on.
From the limited context you gave in your question, I think std::mutex
is the correct approach. Each object that should be independently accessible should have its own mutex protecting it.
Also note that the mutex generally needs to protect writes and reads, since a read happening unsynchronized with a write is a data race and causes undefined behavior, not only unsynchronized writes.
Related Topics
How to Create a Sparse Array in C++
How to Dynamically Allocate a Matrix
Is F(Void) Deprecated in Modern C and C++
C++11: Compile Time Calculation of Array
C++ Data Alignment /Member Order & Inheritance
Sse, Intrinsics, and Alignment
Why Is There No Transform_If in the C++ Standard Library
How to Safely Use Openmp with C++11
Passing Integers as Constant References Versus Copying
How Much Footprint Does C++ Exception Handling Add
How to Construct a Std::String from a Std::Vector<Char>
C++ Compile Time Error: Expected Identifier Before Numeric Constant
Initialization: Parenthesis VS. Equals Sign
How to Make Visual Studio Use the Native Amd64 Toolchain
C++ Inheritance - Inaccessible Base
Getting "Source Type Is Not Polymorphic" When Trying to Use Dynamic_Cast