简体   繁体   English

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

[英]Including std::lock_guard in extra scope

Does is make sense to do something like putting a std::lock_guard in an extra scope so that the locking period is as short as possible? 做一些事情是有意义的,例如将std::lock_guard放在一个额外的范围内,以便锁定时间尽可能短吗?

Pseudo code: 伪代码:

// 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)

Are there more advantages besides having a short lock duration? 除了锁定持续时间短之外还有其他优点吗?

What might be negative side effects? 什么可能是负面影响?

Yes, it certainly makes sense to limit the scope of lock guards to be as short as possible, but not shorter. 是的,将锁定防护装置的范围限制为尽可能短, 但不能更短 ,这当然是有道理的

The longer you hold a lock, the more likely it is that a thread will block waiting for that lock, which impacts performance as is thus usually considered a bad thing. 持有锁的时间越长,线程越有可能阻止等待锁,这会影响性能,因此通常被认为是一件坏事。

However, you must make sure that the program is still correct and that the lock is held at all times when it must be, ie when the shared resource protected by the lock is accessed or modified. 但是,您必须确保程序仍然正确,并且必须始终保持锁定,即访问或修改受锁保护的共享资源时。

There may be one more point to consider (I do not have enough practical experience here to speak with certainty). 可能还有一点需要考虑(我在这里没有足够的实践经验来确定)。 Locking/releasing a mutex can potentially be an operation with nontrivial performance costs itself. 锁定/释放互斥锁可能本身就是一项具有重要性能成本的操作。 Therefore, it may turn out that keeping a lock for a slightly longer period instead of unlocking & re-locking it several times in the course of one operation can actually improve overall performace. 因此,可能会发现,在一次操作过程中保持锁定的时间稍长,而不是解锁并重新锁定它可以实际上提高整体性能。 This is something which profiling could show you. 这是分析可以告诉你的东西。

There might be a disadvantage: you cannot protect initializations this way. 可能存在一个缺点:您无法以这种方式保护初始化。 For example: 例如:

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

you have to use assignment like this: 你必须使用这样的作业:

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

which may be suboptimal for some types, for which default initialization followed by assignment is less efficient than directly initializing. 对于某些类型而言,这可能是次优的,默认初始化后跟分配的效率低于直接初始化。 Moreover, in some situations, you cannot change the variable after initialization. 此外,在某些情况下,您无法在初始化后更改变量。 (eg const variables) (例如const变量)


user32434999 pointed out this solution: user32434999指出了这个解决方案:

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

This way, you can protect the retrieval process, but the initialization itself is still not guarded. 这样,您可以保护检索过程,但初始化本身仍然没有受到保护。

Yes, it makes sense. 是的,这是有道理的。

There are no other advantages, and there are no side-effects (it is a good way to write it). 没有其他优点,也没有副作用(这是编写它的好方法)。

An even better way, is to extract it into a private member function (if you have an operation that is synchronized this way, you might as well give the operation its own name): 更好的方法是将其提取到私有成员函数中(如果您有一个以这种方式同步的操作,您可以将操作赋予其自己的名称):

{
    // 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;
}

Using an extra scope specifically to limit the lifetime of an std::lock_guard object is indeed good practice. 使用额外的作用域来限制std :: lock_guard对象的生命周期确实是一种很好的做法。 As the other answers point out, locking your mutex for the shortest period of time will reduce the chances that another thread will block on the mutex. 正如其他答案所指出的那样,在最短的时间内锁定互斥锁将减少另一个线程阻塞互斥锁的可能性。

I see one more point that was not mentioned in the other answers: transactional operations. 我还看到了其他答案中没有提到的另一点:交易操作。 Let's use the classical example of a money transfer between two bank accounts. 让我们使用两个银行账户之间汇款的经典例子。 For your banking program to be correct, the modification of the two bank account's balance must be done without unlocking the mutex in between . 为了使您的银行业务计划正确,必须在不解锁其间的互斥锁的情况下修改两个银行账户的余额。 Otherwise, it would be possible for another thread to lock the mutex while the program is in a weird state where only one of the accounts was credited/debited while the other account's balance was untouched! 否则,当程序处于奇怪状态时,另一个线程可能会锁定互斥锁,其中只有一个帐户被记入/记入借方而另一个帐户的余额未受影响!

With this in mind, it is not enough to ensure that the mutex is locked when each shared resource is modified. 考虑到这一点,仅在确保在修改每个共享资源时锁定互斥锁是不够的。 Sometimes, you must keep the mutex locked for a period of time spanning the modification of all the shared resources that form a transaction. 有时,您必须将互斥锁保持锁定一段时间,跨越修改形成事务的所有共享资源

EDIT: 编辑:

If for some reason keeping the mutex locked for the whole duration of the transaction is not acceptable, you can use the following algorithm: 如果由于某种原因在整个事务持续期间保持互斥锁被锁定是不可接受的,则可以使用以下算法:
1. Lock mutex, read input data, unlock mutex. 1.锁定互斥锁,读取输入数据,解锁互斥锁。
2. Perform all needed computations, save results locally. 2.执行所有需要的计算,在本地保存结果。
3. Lock mutex, check that input data has not changed , perform the transaction with readily available results, unlock the mutex. 3.锁定互斥锁,检查输入数据是否未更改 ,使用随时可用的结果执行事务,解锁互斥锁。

If the input data has changed during the execution of step 2, throw away the results and start over with the fresh input data. 如果在执行步骤2期间输入数据已更改,则丢弃结果并重新开始输入新数据。

I don't see the reason to do it. 我没有看到这样做的理由。 If you do something so simple as "set one variable" - use atomic<> and you don't need mutex and lock at all. 如果你做的事情如“设置一个变量”那么简单 - 使用原子<>,你根本不需要互斥锁和锁。 If you do something complicated - extract this code into new function and use lock in its first line. 如果你做了一些复杂的事情 - 将这段代码提取到新函数中并在第一行中使用lock。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM