使用锁保护器和互斥锁的首选方式是什么

What is the prefered way of using lock guard and mutex

class 成员函数将在其 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++ 最佳实践进行一些说明。

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

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

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

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

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

所以唯一明智且可移植的方法是将锁放在循环之外。因为即使你关心延迟,std::mutex 也帮不了你。