【问题标题】:unique_lock across threads?跨线程的unique_lock?
【发布时间】:2013-09-21 20:59:26
【问题描述】:

我在概念化unique_lock 应该如何跨线程操作时遇到了一些麻烦。我试着做一个简单的例子来重新创建我通常会使用condition_variable 的东西。

#include <mutex>
#include <thread>
using namespace std;
mutex m;
unique_lock<mutex>* mLock;
void funcA()
{
    //thread 2
    mLock->lock();//blocks until unlock?Access violation reading location 0x0000000000000000.
}

int _tmain(int argc, _TCHAR* argv[])
{
    //thread 1
    mLock = new unique_lock<mutex>(m);
    mLock->release();//Allows .lock() to be taken by a different thread?
    auto a = std::thread(funcA);
    std::chrono::milliseconds dura(1000);//make sure thread is running
    std::this_thread::sleep_for(dura);
    mLock->unlock();//Unlocks thread 2's lock?
    a.join();
    return 0;
}

【问题讨论】:

  • 如果你通常需要条件变量,那么你需要条件变量。 unique_lock 只是互斥锁所有权的抽象。它所做的所有同步都是锁定和解锁互斥锁。
  • @zch 这纯粹是为了了解发生了什么:-)
  • unique_lock 并不是要共享的,只是为了在特定范围上持有锁(它只是互斥锁的 RAII 包装器,仅此而已)。当然,您可以尝试滥用它,并且就滥用而言直言不讳,我认为您确实做到了。 ;)
  • 每个资源都有一个互斥锁(全局);每个线程都有一个锁。

标签: c++ c++11 concurrency locking


【解决方案1】:

unique_lock 不应同时从多个线程访问。它不是以这种方式设计为线程安全的。相反,多个unique_locks(局部变量)引用同一个全局mutex。只有mutex 本身被设计为可以同时被多个线程访问。即便如此,我的声明也不包括~mutex()

例如,有人知道mutex::lock() 可以被多个线程访问,因为它的规范包括以下内容:

同步:先前对同一对象的unlock()操作应(4.7)此操作同步。

其中 synchronize with 是 4.7 [intro.multithread](及其子条款)中定义的艺术术语。

【讨论】:

  • 哇,这可要了我的命。 (一直试图在线程之间使用手动 .unlock()ed unique_lock,因为 condition_variable 需要它。花了一段时间才弄清楚脚枪保护器本身就是一把大脚枪。不过,除了我自己之外,没有人应该受到责备。)跨度>
  • 在任何地方都有对此的引用吗?根据 cppreference,std::unique_lock&lt;std::mutex&gt; 将满足Lockable 的要求,并且可以与std::lock 一起使用(就像互斥锁一样,即 unique_lock 是互斥锁的超集)。我找不到任何定义这是否意味着一次仅来自一个线程。
  • 在标准中,4.7.1 数据竞赛 [intro.races] 详细介绍了线程安全问题。简而言之,它定义了同步操作。这些在整个 std::lib 中通过关于特定操作规范的 Synchronization 子句进行标识。例如mutex::lock() 有一个同步子句。某处(可能在 [intro.races] 下还说const 的同时操作(并且在没有 non-const 的情况下)也是线程安全的。一切 else 不被设计为一次被多个线程调用。
【解决方案2】:

看起来不太对劲。首先,release 是“取消关联互斥体而不解锁它”,这不太可能是您想要在那个地方做的事情。这基本上意味着您的unique_lock&lt;mutex&gt; 中不再有mutex - 这将使其非常无用 - 这可能是您获得“访问冲突”的原因。

编辑:在对您的代码进行一些“按摩”并说服 g++ 4.6.3 做我想做的事情(因此是 #define _GLIBCXX_USE_NANOSLEEP)之后,这是一个工作示例:

#define _GLIBCXX_USE_NANOSLEEP
#include <chrono>
#include <mutex>
#include <thread>
#include <iostream>
using namespace std;
mutex m;
void funcA()
{
    cout << "FuncA Before lock" << endl;
    unique_lock<mutex> mLock(m);
    //thread 2
    cout << "FuncA After lock" << endl;
    std::chrono::milliseconds dura(500);//make sure thread is running
    std::this_thread::sleep_for(dura);        //this_thread::sleep_for(dura);
    cout << "FuncA After sleep" << endl;
}

int main(int argc, char* argv[])
{
    cout << "Main before lock" << endl;
    unique_lock<mutex> mLock(m);
    auto a = std::thread(funcA);
    std::chrono::milliseconds dura(1000);//make sure thread is running
    std::this_thread::sleep_for(dura);        //this_thread::sleep_for(dura);
    mLock.unlock();//Unlocks thread 2's lock?
    cout << "Main After unlock" << endl;
    a.join();
    cout << "Main after a.join" << endl;
    return 0;
}

不知道为什么需要使用new 来创建锁。当然unique_lock&lt;mutex&gt; mlock(m); 应该可以解决问题(当然还有将mLock-&gt; 相应更改为mLock.)。

【讨论】:

    【解决方案3】:

    锁只是一种自动防护,以安全和理智的方式操作互斥锁。

    你真正想要的是这段代码:

    std::mutex m;
    
    void f()
    {
        std::lock_guard<std::mutex> lock(m);
        // ...
    }
    

    这有效地“同步”了对f 的调用,因为进入它的每个线程都会阻塞,直到它设法获得互斥体。

    unique_lock 只是lock_guard 的增强版本:它可以解锁、移动(感谢@MikeVine),它本身就是一个“可锁定对象”,就像互斥锁本身一样,并且因此它可以用于例如可变参数std::lock(...) 以无死锁的方式一次锁定多个事物,并且可以由std::condition_variable 管理(感谢@syam)。

    但除非您有充分的理由使用unique_lock,否则最好使用lock_guard。一旦你需要升级到unique_lock,你就会知道为什么。

    【讨论】:

    • +1。除了您刚才所说的之外,我发现到目前为止我更喜欢unique_lock 而不是lock_guard唯一原因是因为condition_variable 需要unique_lock。这至少涉及 10mloc(只是让 OP 有一个想法)。诚然,只有一小部分是 C++11 就绪的,但这不会改变基本概念和要求。
    • @syam:关于condition_variable 的好点子,谢谢! (这是因为wait 调用需要锁定和解锁,而简单的lock_guard 没有提供。)
    • unique_lock 的另一个有用的特性是它的可移动性,所以你可以说:auto stackLock = Lock();std::lock_guard&lt;std::mutex&gt; stackLock(m_lock); 更简洁[编辑其中Lock 是返回std::unique_lock&lt;std::mutex&gt;(m_lock) 的成员函数]
    • @MikeVine:是的......我通常会说,当你转移锁的所有权时,你可能已经很深了,但你的例子是一个很好的主意。
    【解决方案4】:

    作为旁注,上面的答案跳过了互斥锁的立即锁定和延迟锁定之间的区别:

    #include<mutex>
    ::std::mutex(mu);
    auto MyFunction()->void
    {
       std::unique_lock<mutex> lock(mu); //Created instance and immediately locked the mutex
       //Do stuff....
    }
    
    auto MyOtherFunction()->void
    {
       std::unique_lock<mutex> lock(mu,std::defer_lock); //Create but not locked the mutex
       lock.lock(); //Lock mutex
       //Do stuff....
       lock.unlock(); //Unlock mutex
    }
    

    MyFunction() 显示广泛使用的立即锁,而 MyOtherFunction() 显示延迟锁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-14
      • 2023-03-12
      • 2014-08-15
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 2011-03-02
      • 1970-01-01
      相关资源
      最近更新 更多