将unique_lock<recursive_mutex>移动到另一个线程

4

我想知道当你移动一个持有recursive_mutexunique_lock时会发生什么。

具体来说,我在看这段代码:

recursive_mutex g_mutex;

#define TRACE(msg) trace(__FUNCTION__, msg)

void trace(const char* function, const char* message)
{
    cout << std::this_thread::get_id() << "\t" << function << "\t" << message << endl;
}

future<void> foo()
{
    unique_lock<recursive_mutex> lock(g_mutex);
    TRACE("Owns lock");
    auto f = std::async(launch::async, [lock = move(lock)]{
        TRACE("Entry");
        TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!"); // Prints Owns lock! 
        this_thread::sleep_for(chrono::seconds(3));
    });
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!"); // Prints Doesn't own lock! 
    return f;
}


int main()
{
    unique_lock<recursive_mutex> lock(g_mutex);
    TRACE("Owns lock");
    auto f = foo();    
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!");        // Prints Owns lock! 
    f.wait();
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!");        // Prints Owns lock!
}

这段示例代码的输出让我感到非常惊讶。主函数中的unique_lock如何知道线程已经释放了互斥锁?这是真的吗?

3
不清楚您感到惊讶的是什么。unique_lock 中有一个简单的布尔成员,它返回 owns_lock(),并且移动构造函数以可预测和记载的方式将其移动。owns_lock() 不会触及底层互斥锁。话虽如此,您的程序表现出未定义的行为:当在工作线程上销毁 unique_lock 时,它调用 g_mutex.unlock(),但工作线程没有持有 g_mutex 的锁(这是 unlock() 的前提条件)。 - Igor Tandetnik
1
@IgorTandetnik 谢谢。那么在线程之间移动recursive_mutex的所有权是不可能的吗?如果互斥量不是递归的呢?将unique_lock移动到所有者线程是否真的会移动所有权? - Mr. Anderson
4
unique_lock对象在不同线程之间移动对你没有任何好处。要意识到,unique_lock实际上只是一个mutex*指针和一个bool owns标志 - 没有黑魔法。移动构造函数只是移动这个指针和布尔值。在调用了my_mutex.lock()的线程不同于调用my_mutex.unlock()的线程时,无论是直接调用还是通过欺骗unique_lock来间接调用,都会导致未定义行为。这适用于所有类型的互斥锁。 - Igor Tandetnik
@IgorTandetnik,我认为你已经回答了这个问题。为什么不把你的综合评论提交为答案呢? - jonspaceharper
1个回答

9
你似乎把unique_lock赋予了一些神奇的属性。它实际上并没有,它只是一个非常简单的类。它有两个数据成员:Mutex* pmbool owns(成员名称仅用于说明)。lock()只是pm->lock(); owns = true;,而unlock执行的是pm->unlock(); owns = false;。析构函数是if (owns) unlock();。移动构造函数会复制这两个成员,并将它们在原始对象中分别设置为nullptrfalseowns_lock()返回owns成员的值。
所有线程同步的魔法都在互斥量本身以及其lock()unlock()方法中。unique_lock只是它周围的一个薄包装。
现在,调用mutex.unlock()的线程必须预先持有该互斥量(也就是说,该线程之前已经调用了lock()),否则程序会表现出未定义的行为。无论您是显式地调用unlock,还是欺骗一些辅助程序(如unique_lock)为您调用它,这都是真实的。
基于这一切,将unique_lock实例移动到另一个线程只会在短时间内触发未定义的行为;没有任何好处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接