How to Use Something Like Std::Vector<Std::Mutex>

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:

  1. You need to use std::lock(m1, m2, ...) to reliably lock more than one mutex, or re-invent an algorithm such as std::lock to avoid deadlock. One such alternative algorithm is if you can guarantee that everyone always locks the mutexes in mutexes in the same order (say by index), then you don't need std::lock at all, just loop thru and lock `em.

  2. lock_guard is problematic to put in vector one at a time as vector<T>::emplace_back requires T to be move constructible. That is one of the reasons why unique_lock works here and lock_guard doesn't. mutexes gets away with holding non-movable mutexes because it constructs the vector all at once instead of adding to it with emplace_back.

  3. In this example locks holds references into mutexes. Make sure you don't have lifetime issues between these two containers (mutexes must outlive locks).

  4. If you need to add non-movable items to the end of a sequence, switch to deque, that will work where vector won't.

  5. 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.

  6. 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 large n.

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 where vector 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_ptrs (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 mutex

std::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



Leave a reply



Submit