【问题标题】:Toggling mutex lock using guard使用守卫切换互斥锁
【发布时间】:2016-12-06 14:56:17
【问题描述】:

如何重构以下代码以使用推荐的 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

【问题讨论】:

  • 您能否提供一个 Python 示例,其中包含 with 和您希望在 C++ 中使用的互斥锁?
  • 你想sync()是什么意思?只是切换一个锁?
  • 糟糕的设计需要糟糕的代码。 根本不需要进行锁定切换。
  • @michalsrb 我添加了一个示例 @RichardHodges 是的,我希望在 while 循环的一次迭代中保持锁定,然后释放。
  • @SergeyA 例如,如何同步多个参与者写入文件?

标签: c++ mutex


【解决方案1】:

只需创建一个lockerstd::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
}

【讨论】:

    【解决方案2】:

    只需使用范围:

    {
        std::lock_guard<std::mutex> lock{mutex};
    
        // Your operations here
    }
    

    如果你真的想拥有一个带头的作用域,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
    }
    

    ...但实际上,一个简单的范围可以正常工作,并且不必记录在案并反对代码审查员。

    【讨论】:

    • c++17 与否,第二个想法需要放入垃圾箱。它上面写满了“令人困惑的代码”。
    • @RichardHodges 那么你肯定不会喜欢那个编辑 :)
    • @AnonymousEntity 如果你使用#define 方法,你的同事会要求你被解雇​​。
    【解决方案3】:

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

    没有人需要切换锁。

    【讨论】:

    • 在我看来,在这种情况下您仍在切换锁,但是让有关范围的 c++ 规则处理切换而不是显式地进行切换。显式或隐式地,互斥锁仍在切换中。
    【解决方案4】:

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

    #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
    

    【讨论】:

      【解决方案5】:

      这是一个非常邪恶但有效的黑客:

      #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 循环头

      • 为持有互斥锁的lock_guard 创建一个unique_ptr
      • 在循环条件下,验证指针是否为非空
      • 在增量部分,重置指针。

      这具有执行紧跟在with() 宏之后的循环体的效果,同时保持锁。由于宏和指针的原因,有点hackish,但仍然比构造while 循环和切换互斥锁状态要干净一些。

      如果您有 C++14,则可以使用 std::make_unique 稍微简化宏。如果能用C++17,Quentin's solution会更优雅。

      当然,这并不是真正的 C++ 方式,只是为了获得一些语法糖。因此,除非您真的坚持遵循类似 Python 的语法,否则您可以像这样使用 lock_guard 的标准 C++ 方式:

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

      【讨论】:

      • 为什么不让它成为非邪恶非黑客?
      • 嗯,你喜欢它吗?它模拟具有类似 Python 行为的 with 语句。但这并不是真正的 C++ 方式。因此邪恶和黑客。
      • @RichardHodges 那更好吗?
      • 如果它包含#define,这是对全局命名空间的不可维护的污染,这将导致一旦编译的程序突然不再编译。看在上帝的份上,使用内联函数或模板函数。
      • 当然有更简洁的方法,毕竟我说“邪恶”和“黑客”是有原因的。但这是准确再现 Python 风格的唯一方法。根据我们提供的替代方案,OP 是否可以接受这是他们的决定。虽然没有理由不高兴。 :)
      猜你喜欢
      • 1970-01-01
      • 2021-06-29
      • 1970-01-01
      • 2018-05-23
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      • 2010-12-17
      • 1970-01-01
      相关资源
      最近更新 更多