在额外范围内包括std :: lock_guard

时间:2019-01-28 10:13:58

标签: c++ locking mutex

做一些有意义的事情,例如将std::lock_guard放在一个额外的范围内,以使锁定期尽可能短吗?

伪代码:

// all used variables beside the lock_guard are created and initialized somewhere else
...// do something

{ // open new scope
    std::lock_guard<std::mutex> lock(mut);
    shared_var = newValue;  
} // close the scope

... // do some other stuff (that might take longer)

除了锁定时间短以外还有其他优势吗?

什么可能是负面副作用?

5 个答案:

答案 0 :(得分:26)

是的,将锁保护装置的范围限制为尽可能短,但不能更短。

您持有锁的时间越长,线程阻塞该锁的可能性就越大,这会影响性能,因此通常认为这是一件坏事。

但是,您必须确保程序仍然正确,并且必须始终保持该锁,即在访问或修改该锁保护的共享资源时始终保持该锁。

可能还需要考虑一点(我在这里没有足够的实践经验来确定地说)。锁定/释放互斥锁可能本身就是一项具有不小的性能成本的操作。因此,可能会发现,在一个操作过程中,将锁保持更长的时间,而不是多次解锁并重新锁定,实际上可以改善整体性能。配置文件可以显示这些内容。

答案 1 :(得分:12)

可能有一个缺点:您不能以这种方式保护初始化。例如:

{
    std::lock_guard<std::mutex> lock(mut);
    Some_resource var{shared_var};
} // woops! var is lost

您必须使用这样的分配:

Some_resource var;
{
    std::lock_guard<std::mutex> lock(mut);
    var = shared_Var;
}

您可能知道,这不是那么好,因为对于某些类型,虚拟初始化(我故意避免使用术语“默认初始化”),然后赋值的效率不如直接初始化。 (我故意避免使用术语“直接初始化”。)此外,在某些情况下,初始化后无法更改变量。 (例如const个)


@P i指出了此解决方案:

// use an immediately-invoked temporary lambda
Some_resource var {
    [&] {
        std::lock_guard<std::mutex> lock(mut);
        return shared_var;
    } () // parentheses for invoke
};

这样,通过返回值优化,几乎所有流行的现代编译器都可以很好地正确实现它,并且从C ++ 17开始强制执行此返回值优化,

答案 2 :(得分:1)

是的,这很有意义。

没有其他优点,也没有副作用(这是编写它的好方法)。

一种更好的方法是将其提取到私有成员函数中(如果您具有以这种方式同步的操作,则不妨为该操作指定自己的名称):

{
    // all used variables beside the lock_guard are created and initialized somewhere else
    ...// do something

    set_var(new_value);

    ... // do some other stuff (that might take longer)
}

void your_class::set_value(int new_value)
{
    std::lock_guard<std::mutex> lock(mut);
    shared_var = new_value;
}

答案 3 :(得分:1)

使用额外的作用域专门限制std :: lock_guard对象的生存期确实是一种好习惯。正如其他答案指出的那样,将互斥锁锁定在最短的时间内将减少另一个线程在互斥锁上阻塞的机会。

我看到其他答案中未提及的另一点:事务性操作。让我们使用在两个银行帐户之间进行转帐的经典示例。为了使您的银行程序正确无误,必须修改两个银行帐户的余额,而无需解锁两者之间的互斥体。否则,当程序处于怪异状态时,另一个线程可能会锁定互斥锁,在该状态下,只有一个帐户被贷记/借记而另一个帐户的余额未受影响!

请记住,仅修改每个共享资源时,确保互斥体被锁定是不够的。有时,您必须将互斥锁锁定一段时间,以修改形成交易的所有共享资源。

编辑:

如果由于某种原因在整个交易过程中都无法保持互斥锁处于锁定状态,则可以使用以下算法:
 1.锁定互斥锁,读取输入数据,解锁互斥锁。
 2.执行所有需要的计算,将结果保存在本地。
 3.锁定互斥锁,检查输入数据未更改,以易于获得的结果执行事务,解锁互斥锁。

如果在执行步骤2的过程中输入数据发生了变化,则丢弃结果并重新使用新的输入数据。

答案 4 :(得分:-1)

我不知道这样做的原因。 如果您执行“设置一个变量”这样的简单操作,请使用atomic <>,并且根本不需要互斥锁和锁定。如果您做的很复杂,请将此代码提取到新函数中,并在第一行使用锁。