4

I have some function which trying to lock std::mutex and if mutex successfully locked, function creates std::thread with lambda-function as thread function parameter, and also passes it lock using std::move():

static std::mutex mtx;
// some mutex defended stuff

void someFunc() {
    // try lock mutex using unique_lock
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
    if(!lock.owns_lock()) {
        return; // locking fails
    }

    // task for thread
    auto task = [&](std::unique_lock<std::mutex>&& lock) {
        // do async work and release lock when it done
        lock.unlock();

        // do something after releasing lock
    };

    // create thread, pass lock 
    std::thread taskThread(task, std::move(lock));
    taskThread.detach();
}

I've got compiler error:

<lambda_1918cc58d906c210588b1a8bb33f1b0d>::operator
()(std::unique_lock<_Mutex> &&) const' : cannot convert parameter 1 from
'std::unique_lock<_Mutex>' to 'std::unique_lock<_Mutex> &&'

How should I proper pass std::unique_lock to my lambda?

UPD: In fact this function is a native C API libaray call, it tryes to start some library operation async and returns control immediately in any case, returning some return code to caller. This code is simplified to concentrate on particaular issue.

vard
  • 2,142
  • 4
  • 30
  • 40
  • possible duplicate of http://stackoverflow.com/questions/4871273/passing-rvalues-through-stdbind – Slava Aug 21 '14 at 14:22
  • What are you trying to achieve with this? You probably shouldn't be passing `lock` into the thread. It already has access to 'mtx' which is all it should need. – dohashi Aug 21 '14 at 14:22
  • Is it really vital that you keep the lock throughout the whole time? With that I mean, do you risk data corruption if the step of launching the async task and its execution is not perceived as atomic by someone awaiting the result? It seems to me like you might want to do this because you use the lock status of the mutex to monitor the status of the async execution, which is an anti-pattern. – ComicSansMS Aug 21 '14 at 14:23
  • Please a `;` after the the definition of your lambda ! – Christophe Aug 21 '14 at 14:24
  • @dohashi I modified my question, look at UPD – vard Aug 21 '14 at 14:29
  • In your lambda you capture all references. You could write it without the parameter lock (and remove the move()) it will compile – Christophe Aug 21 '14 at 14:31
  • @vard please clarify your question, you want to know how to pass that lock as rvalue reference, or how to pass it in general? – Slava Aug 21 '14 at 14:32
  • What compiler and version are you using? [Your code compiles](http://coliru.stacked-crooked.com/a/3445314900bf738a) on gcc-4.9 and clang-3.4 – Praetorian Aug 21 '14 at 14:32
  • 1
    @Christophe If he did that, both the lambda and the `unique_lock` within `somefunc()` would `unlock()` the mutex, which is undefined behavior. – Praetorian Aug 21 '14 at 14:35
  • @Slava std::unique_lock can be passed using only move semantics, don't it? – vard Aug 21 '14 at 14:35
  • @Praetorian using MSVC12, I didn't tried neither gcc not clang yet. – vard Aug 21 '14 at 14:37
  • @Praetorian yes right: it compiles but it doesn't work, and with bad luck the reference of the lock might even be invalid at the time the thread would try to use it. Sorry for the bad idea ! – Christophe Aug 21 '14 at 14:39
  • @Praetorian as I said this is simplified version of code I use, I need to check it now. – vard Aug 21 '14 at 14:43
  • @vard how to pass rvalue reference is answered in the subject I mentioned before. But there could be another solution how to pass this lock (for example using `std::shared_ptr`) – Slava Aug 21 '14 at 14:51
  • 2
    Hmm, I just realized your code has undefined behavior even if it does compile. You're locking and unlocking the `mutex` from different threads, which is not allowed. The thread that unlocks it must be the same one that locks it. Anyway, you code doesn't compile on VC12 because of [this bug](https://connect.microsoft.com/VisualStudio/feedback/details/729886) – Praetorian Aug 21 '14 at 14:56
  • @Slava that could be a nice idea, but more complicated in this particular case. But in spotlight of Praetorian comment I suggest I should use that solution. – vard Aug 21 '14 at 14:56
  • @Praetorian that could be a perfect answer for this question – vard Aug 21 '14 at 14:59
  • I did post it as an answer (now deleted), along with a workaround involving `unique_lock::release` but then realized both the original and workaround have the same problem :) – Praetorian Aug 21 '14 at 15:00

1 Answers1

9

Your code compiles on both gcc and clang. It fails to compile on VS2013 (VC12) because of a bug in the implementation that requires the arguments to the std::thread constructor to be copyable. This results in the compiler attempting to copy the std::unique_lock, which, of course, fails.

However, your code has undefined behavior because you're locking and unlocking the mutex in two different threads, which is not allowed. The thread unlocking it must be the same as the one that had previously locked it.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Could you suggest any workaround about locking/unlocking in different threads? – vard Aug 21 '14 at 15:10
  • @vard You might be able to use `std::future` and `std::promise` to have the original thread unlock the mutex after waiting on the worker thread later, but I don't know enough about that part of the standard library to suggest something more concrete. – Praetorian Aug 21 '14 at 15:13
  • 1
    using that primitives main thread will be blocked until future result becomes ready, AIK. Unfortunately, I need to return control to caller immediately after creatung a thread. I could try to lock mutex in thread and use conditional variable to inform main thread about lockong result. – vard Aug 21 '14 at 15:22
  • @vard Yeah you might be able to get the `condition_variable` approach to work. Another possibility is if `somefunc()` doesn't need to do anything under lock in between acquiring the lock and launching the thread, then you might be able to move the lock acquisition into the thread task. It'll then try to acquire lock and return early if unable to do so. The semantics are slightly different than your example, but maybe that still works for you. – Praetorian Aug 21 '14 at 16:34