Home > OS >  How to transfer a mutex containing object from one function to another?
How to transfer a mutex containing object from one function to another?

Time:09-06

#include <mutex>

class ConcurrentQueue {
    // This class contains a queue and contains functions push to the queue and pop from the queue done in a thread safe manner.
    std::mutex m;
};

class Producer {
    // This class contains several methods which take some ConcurrentQueue objects and then schedule tasks onto it.
public:
    void func(ConcurrentQueue a) {}
};

class Consumer {
    // This class contains several methods which take the same ConcurrentQueue objects and then remove the tasks and complete them one by one.
public:
    void func(ConcurrentQueue a) {}
};

int main() {
    // Here I want to generate the necessary ConcurrentQueue objects and then start threads for producer and consumer methods where I supply it with the required queue objects.
    ConcurrentQueue a;
    Producer b;

    // :( Unfortunately I cannot pass in any of my ConcurrentQueue objects to the methods as apparantly I cannot copy transfer a mutex.
    b.func(a); // This line gives compiler error saying the copy constructor is deleted.
    
    return 0;
}

The above code explains the whole situation through comments. How do I design it better so that I am able to achieve this?

CodePudding user response:

If you don't want your ConcurrentQueue class to be copyable, then don't pass it by value; use pass-by-reference (i.e. arguments of types const ConcurrentQueue & or ConcurrentQueue &) instead.

OTOH if you actually want your ConcurrentQueue class to be copyable (and you should carefully consider whether or not allowing the copying of a ConcurrentQueue object would be helpful or harmful to your goals), you can simply add a copy-constructor to your ConcurrentQueue class that copies the other member-variables but doesn't try to copy the std::mutex:

class ConcurrentQueue {
    // This class contains a queue and contains functions push to the queue and pop from the queue done in a thread safe manner.
    std::mutex m;
    std::queue<int> q;

public:
    ConcurrentQueue()
    {
       // default constructor
    }

    ConcurrentQueue(const ConcurrentQueue & rhs)
    {
       // serialize access to rhs.q so we can read its 
       // contents safely while copying them into this->q
       const std::lock_guard<std::mutex> lock(const_cast<std::mutex &>(rhs.m));
       q = rhs.q;
    }
};

Note that I needed to add a default-constructor as well, since once you add any kind of constructor to the class, the compiler will no longer automatically create a default-constructor for you anymore. If you're using C 11 or later, you could declare the default-constructor via ConcurrentQueue() = default; instead.

CodePudding user response:

Although Jeremy Friesner has given some good information, I think it's worth adding some advice that's a little more direct.

In this case you almost certainly do not want to copy any queue. If you copy a queue, you'll end up with the producer putting data into one queue, and the consumer trying to get data from an entirely separate queue. For what you seem to be trying to accomplish, that's not going to accomplish anything useful.

For a producer/consumer situation, you almost always want a single queue, so the producer can push data into the queue and the consumer will retrieve data from that same queue.

That leaves three options for how to do things:

  • Producer owns queue, consumer gets a reference to the queue.
  • Consumer owns queue, producer gets a reference to the queue.
  • Parent of both owns queue, passes reference to both producer and consumer.

[note: in this case, "reference" could really mean "pointer".]

At least at first, I'd generally advise the last of these. This keeps lifetime management fairly easy. Especially if the producer owns the queue, the initial temptation is for the producer to be destroyed after it finishes its job, but you need to assure the queue isn't destroyed until the consumer has consumed all the data, so you need a side-channel backward from the consumer to the producer to signal when the queue can be destroyed.

When the parent creates the queue, about all it has to do is define an instance of the queue, and pass it to the producer and the consumer. Then after they're done it returns (or whatever) and the queue gets destroyed more or less automatically, but not until both children are done with it.

  • Related