使用守卫切换互斥锁

Toggling mutex lock using guard

如何重构以下代码以使用推荐的 lock_guards?

bool locked = false;
bool sync() {
    if (locked) {
        mutex.unlock();
    } else {
        mutex.lock();
    }
    locked = !locked;
    return locked;
}

所需的使用模式:

while (sync()) {
    // do safe things
}

基本上我是在尝试模仿 Python 中的 with 语句。示例:

from multiprocessing import Lock
with Lock():
    # do safe things

这是一个非常邪恶但有效的技巧:

#include <memory>
#include <mutex>
#include <iostream>

#define with(m) for (std::unique_ptr<std::lock_guard<std::mutex>> lock( \
                     new std::lock_guard<std::mutex>(m)); lock; lock.reset())

int main()
{
  std::mutex m;
  with (m)
  {
    std::cout << "got the mutex" << std::endl;
  }
}

with(m) 扩展为 for 循环 header 其中

  • 创建一个 unique_ptrlock_guard 持有互斥锁
  • 在循环条件中,验证指针是non-null
  • 在增量部分,重置指针。

这具有执行循环 body 的效果,该循环紧跟在 with() 宏之后一次,并持有锁。由于宏和指针的缘故,有点 hackish,但比构建 while 循环和切换互斥锁状态要干净一些。

如果你有 C++14,你可以使用 std::make_unique. If you can use C++17, Quentin's solution 稍微简化一下宏,这样会更优雅。

当然,这并不是真正的 C++ 实现方式,只是获取一些语法糖的 hack。所以,除非你真的坚持遵循 Python-like 语法,否则你可以使用标准的 C++ 方式使用 lock_guard ,如下所示:

{
  std::lock_guard<std::mutex> lock(my_mutex);
  // Do whatever
}

只需创建一个locker std::lock_guard<std::mutex> lock(mutex);,互斥锁将在lock生命周期结束时自动释放。

std::mutex mutex;
....
{
     std::lock_guard<std::mutex> lock(mutex);
     // do do safe things
     // mutex will be released here
}

锁卫不支持您的设计,这是有充分理由的。您永远不需要 切换 锁定。共享资源的正确使用模式是每个访问资源的线程首先通过守卫获取锁,然后使用资源,然后存在 - 这会触发自动锁释放。

没有人需要打开锁。

只需使用范围:

{
    std::lock_guard<std::mutex> lock{mutex};

    // Your operations here
}

如果你真的想要一个带有 header 的范围,C++17 的 if-with-initializer 可以很容易地弯曲成那个形状:

if(std::lock_guard<std::mutex> lock{mutex}; true) {
    // Your operations here
}

...然后你可以将它隐藏在一个(精心命名的)宏中。

最后,我拒绝对你如何使用那个东西负责,这里是一个 C++14 实现:

template <class T>
struct Lock {
    Lock(T &mtx)
    : guard{mtx} { }

    constexpr operator bool() const { return false; }

    std::lock_guard<T> guard;
};

// Replace the pragmas for a compiler other than Clang or GCC
// so it doesn't complain about the unused variable
#define withLock(mtx) \
    _Pragma("GCC diagnostic push") \
    _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") \
    if(auto const &_lockGuard = Lock<std::remove_reference_t<decltype(mtx)>>{mtx}); else \
    _Pragma("GCC diagnostic pop")

// ...

withLock(mutex) {
    // Your operations here
}

...但实际上,一个简单的作用域就可以正常工作,并且不必记录在案并与代码审查人员争论。

我通常如何处理它的完整示例:

#include <mutex>
#include <future>
#include <iostream>
#include <vector>
#include <chrono>
#include <random>

// here is the relevant function

template<class Mutex, class F>
decltype(auto) with_lock(Mutex& mutex, F&& func)
{
    using mutex_type = std::decay_t<Mutex>;
    using lock_type = std::lock_guard<mutex_type>;
    lock_type lock { mutex };
    return func();
}

// here is a test

int main()
{
    std::mutex m;
    std::default_random_engine eng { std::random_device()() };

    std::vector<std::future<void>> futures;
    for (int i = 0 ; i < 100 ; ++i)
    {
        futures.push_back(std::async(std::launch::async, [i, &m, &eng]
        {
            std::uniform_int_distribution<int> dist(10, 100);
            std::this_thread::sleep_for(std::chrono::milliseconds(dist(eng)));
            with_lock(m, [i]
            {
                std::cout << "thread index: " << i << std::endl;
            });
        }));
    }

    for (auto& f : futures)
    {
        f.get();
    }
}

示例输出(注意,cout 上没有比赛):

thread index: 63
thread index: 62
thread index: 30
thread index: 49
thread index: 25
thread index: 1
thread index: 58
thread index: 33
thread index: 72
thread index: 75
thread index: 11
thread index: 22
thread index: 46
thread index: 41
thread index: 20
thread index: 36
thread index: 37
thread index: 23
thread index: 45
thread index: 82
thread index: 0
thread index: 28
thread index: 88
thread index: 3
thread index: 74
thread index: 84
thread index: 31
thread index: 9
thread index: 34
thread index: 93
thread index: 24
thread index: 98
thread index: 38
thread index: 55
thread index: 43
thread index: 52
thread index: 40
thread index: 69
thread index: 67
thread index: 91
thread index: 89
thread index: 86
thread index: 76
thread index: 21
thread index: 29
thread index: 53
thread index: 81
thread index: 10
thread index: 96
thread index: 68
thread index: 7
thread index: 73
thread index: 78
thread index: 54
thread index: 59
thread index: 83
thread index: 60
thread index: 47
thread index: 19
thread index: 6
thread index: 17
thread index: 56
thread index: 57
thread index: 66
thread index: 70
thread index: 39
thread index: 26
thread index: 13
thread index: 79
thread index: 15
thread index: 5
thread index: 94
thread index: 14
thread index: 77
thread index: 32
thread index: 48
thread index: 87
thread index: 92
thread index: 61
thread index: 80
thread index: 18
thread index: 27
thread index: 12
thread index: 71
thread index: 4
thread index: 2
thread index: 99
thread index: 35
thread index: 50
thread index: 51
thread index: 65
thread index: 64
thread index: 16
thread index: 42
thread index: 90
thread index: 8
thread index: 44
thread index: 85
thread index: 97
thread index: 95