【问题标题】:How to std::mutex::lock until function returns如何 std::mutex::lock 直到函数返回
【发布时间】:2014-12-09 16:43:41
【问题描述】:

我想返回一个std::vector。这个std::vector 可以被其他线程访问(读和写)。如何在函数完成返回后立即解锁我的std::mutex

例如:

// Value.cpp
std::vector<int> GetValue()
{
  std::lock_guard<std::mutex> lock(mutex);

  // Do super smart stuff here
  // ...

  return m_value;
}

// MyThread.cpp
auto vec = myVec.GetValue();

现在如果“在这里做超级聪明的事情”为空怎么办:

// Value.cpp
std::vector<int> GetValue()
{
  std::lock_guard<std::mutex> lock(mutex);
  return m_value;
}

// MyThread.cpp
auto vec = myVec.GetValue();

那么锁还是强制的吗?为什么?

【问题讨论】:

  • vec 的复制构造发生在锁之外。如果其他线程可能正在修改引用的向量,那么您就有了数据竞争。
  • 这不起作用,您正在通过引用返回一些内部状态 (m_value)。
  • @DieterLücking:确实,我在实际添加代码时注意到了这一点^^' 即使 m_value 是一个成员(所以它应该实际工作),按值返回可以解决我的问题(即使我仍然需要使用 lock_guard<:mutex>)。我添加的实际上是一个未解决的问题。谢谢。
  • 如果按值返回,一切都很好。见stackoverflow.com/questions/8437763/…
  • @DieterLücking:是的,确实如此。我不记得为什么我首先通过 const 引用返回。谢谢!

标签: c++ concurrency thread-safety


【解决方案1】:

使用std::lock_guard 来处理通过 RAII 锁定和解锁mutex,这就是它的真正用途。

int foo()
{
    std::lock_guard<std::mutex> lg(some_mutex);  // This now locked your mutex
    for (auto& element : some_vector)
    {
        // do vector stuff
    }
    return 5;
}  // lg falls out of scope, some_mutex gets unlocked

foo 返回后,lg 将超出范围,unlock some_mutex 会超出范围。

【讨论】:

  • 这正是我想要的。谢谢。
  • 如果我有类似的东西怎么办:auto vec = MyFunc(); operator= 可能会将 MyFunc() 返回的向量插入到 vec 中,而线程会修改返回的向量?
  • 那么听起来你可能有一个设计问题需要考虑。不看具体例子很难给出答案。
【解决方案2】:

这是 print 语句可以真正帮助解决的问题。例如:

#include <mutex>
#include <iostream>

std::mutex mut;

template <class Mutex>
class Lock
{
    Mutex& mut_;
public:
    ~Lock()
    {
        std::cout << "unlock\n";
        mut_.unlock();
    }

    Lock(const Lock&) = delete;
    Lock& operator=(const Lock&) = delete;

    Lock(Mutex& mut)
        : mut_(mut)
    {
        mut_.lock();
        std::cout << "lock\n";
    }
};

struct A
{
    ~A()
    {
        std::cout << "~A() : " << this << "\n";
    }

    A()
    {
        std::cout << "A() : " << this << "\n";
    }

    A(const A& a)
    {
        std::cout << "A(const A&) : " << this << ", " << &a << "\n";
    }

    A& operator=(const A& a)
    {
        std::cout << "A& operator=(const A&) : " << this << ", " << &a << "\n";
        return *this;
    }
};

A a;

A
get()
{
    Lock<std::mutex> lk(mut);
    return a;
}

int
main()
{
    std::cout << "Start\n";
    auto vec = get();
    std::cout << "End\n";
}

通过制作我自己的std::lock_guard 版本,我可以插入打印语句来找出互斥锁何时被锁定和解锁。

通过制作一个假的std::vector(上面称为A),我可以将打印语句插入到我感兴趣的特殊成员中。对我来说,这个输出:

A() : 0x10fcfb188
Start
lock
A(const A&) : 0x7fff4ff06b28, 0x10fcfb188
unlock
End
~A() : 0x7fff4ff06b28
~A() : 0x10fcfb188

这清楚地表明互斥体在复制 0x10fcfb188 处的 A 时被锁定。

可以通过以下方式更改测试以分配:

int
main()
{
    A vec;
    std::cout << "Start\n";
    vec = get();
    std::cout << "End\n";
}

现在输出:

A() : 0x10d8a7190
A() : 0x7fff5235ab28
Start
lock
A(const A&) : 0x7fff5235ab18, 0x10d8a7190
unlock
A& operator=(const A&) : 0x7fff5235ab28, 0x7fff5235ab18
~A() : 0x7fff5235ab18
End
~A() : 0x7fff5235ab28
~A() : 0x10d8a7190

起初看起来赋值发生在锁之外,因此看起来不安全。然而,经过仔细检查,可以看到位于 0x10d8a7190 的受保护A 被复制到锁内的临时A。然后互斥锁被解锁,并从临时到本地进行分配。没有其他线程可以引用临时。所以只要没有其他线程引用vec,这又是安全的。

【讨论】:

  • 非常好的解释。谢谢!
  • 很棒的演示。非常感谢!
猜你喜欢
  • 2017-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-24
  • 2021-07-28
  • 2013-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多