【问题标题】:What is the prefered way of using lock guard and mutex使用锁守卫和互斥锁的首选方式是什么
【发布时间】:2018-04-05 07:32:41
【问题描述】:

类成员函数将在其critical sectioncritical data 上使用mutexlock_guard。我可以看到这可以通过两种不同的方式完成。

案例 1: - 在 for 循环内部。 lock_guard 在每次迭代时被构造和销毁。

std::mutex s_mutex;

class Foo {
public:
    void bar() {
        for ( ... ) {
            std::lock_guard<std::mutex> guard( s_mutex );
            // critical section data
        } // lock_guard goes out of scope and releases or unlocks mutex
    }
};

案例 2: - 在 for 循环之外。 lock_guard 创建一次,循环完成后销毁。

std::mutex s_mutex;

class Foo {
public:
    void bar() {
        std::lock_guard<std::mutex> guard( s_mutex );
        for ( ... ) {
            // data
        }
    } // lock_guard goes out of scope releasing or unlocking mutex.
};

我知道在第一种情况下,一个线程可以在一次迭代中访问循环,而不同的线程可以在不同的迭代中访问循环,但是没有两个线程可以同时访问临界区。至于第二种情况,我确实知道如果一个线程正在访问循环,那么第二个线程在完全完成之前无法触及该循环。

一种方法比另一种更可取还是取决于使用意图?两者之间是否存在性能影响?只是想对尝试维护现代 c++ 最佳实践进行一些澄清。

【问题讨论】:

    标签: multithreading locking mutex c++17


    【解决方案1】:

    您正在解锁互斥锁以在之后立即将其锁定。会发生什么取决于互斥锁的实现方式,但典型的非公平实现会唤醒一个等待的线程,但会在该线程能够运行之前获取互斥锁,从而浪费执行时间。

    如果您的互斥锁实现是公平的(想想ticket lock),那么您的线程将无法在解锁互斥锁后锁定它,并且必须等到另一个线程离开临界区。这意味着在争用情况下,您的线程将不得不在每次迭代时进行上下文切换,从而浪费执行时间。

    所以第二种情况(循环外的互斥锁)在公平和非公平互斥锁实现中应该更有效,这就是你应该做的。

    如果您关心延迟,现在您可以考虑在每次迭代时锁定互斥锁,因为这允许其他线程运行,但这仅在公平互斥锁实现时才有意义。

    C++ 没有说明std::mutex 是否公平,而且大多数实现都不公平。所以不要期望太多。

    因此,唯一合理且可移植的方法是将锁放在循环之外。因为即使您关心延迟,std::mutex 也无济于事。

    【讨论】:

    • 感谢您的澄清。我倾向于外部方法。在阅读一些文档以了解如何在我的代码中正确实现 mutex 和 lock_guard 时;我见过他们在 for 循环中声明互斥锁的例子。他们的理由是,对于互斥锁数据,他们强调您希望范围尽可能小并尽可能接近实际关键部分 - 尽可能关键数据的想法。如果我们不使用互斥锁而是使用信号量,这可能是有道理的......这对一个可靠的解释很有帮助。
    猜你喜欢
    • 1970-01-01
    • 2021-06-29
    • 1970-01-01
    • 2018-05-23
    • 2012-03-12
    • 2011-04-13
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多