簡體   English   中英

具有多個成員函數的實例的線程安全鎖定

[英]Thread-safe locking of instance with multiple member functions

我有一個被多個線程使用的結構實例。 每個線程包含未知數量的 function 調用,這些調用會更改結構成員變量。

我有一個專用的 function 試圖為當前線程“保留”結構實例,我想確保在原始線程允許之前沒有其他線程可以保留該實例。

互斥鎖浮現在腦海中,因為它們可用於保護資源,但我只知道單個 function 的 scope 中的 std::lock_guard,但不為鎖定和解鎖之間的所有 function 調用添加保護。

當我知道它總是按順序調用保留和釋放時,是否可以保護這樣的資源?

更好地解釋它的片段:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex


struct information_t {
    std::mutex mtx;
    int importantValue = 0;

    // These should only be callable from the thread that currently holds the mutex
    void incrementIt() { importantValue++; }
    void decrementIt() { importantValue--; }
    void reset() { importantValue = 0; }

} protectedResource; // We only have one instance of this that we need to work with

// Free the resource so other threads can reserve and use it
void release()
{
    std::cout << "Result: " << protectedResource.importantValue << '\n';
    protectedResource.reset();
    protectedResource.mtx.unlock(); // Will this work? Can I guarantee the mtx is locked?
}

// Supposed to make sure no other thread can reserve or use it now anymore!
void reserve()
{ 
    protectedResource.mtx.lock();
}

int main()
{
    std::thread threads[3];
    
    threads[0] = std::thread([]
    {
            reserve();
            protectedResource.incrementIt();
            protectedResource.incrementIt();
            release();
    });

    threads[1] = std::thread([]
        {
            reserve();
            // do nothing
            release();
        });

    threads[2] = std::thread([]
        {
            reserve();
            protectedResource.decrementIt();
            release();
        });

    for (auto& th : threads) th.join();

    return 0;
}

我對每條評論的建議:

一個更好的習慣用法可能是一個監視器,它可以鎖定您的資源並為所有者提供訪問權限。 要獲取資源, reserve()可以返回此類監視器 object(類似於訪問資源內容的代理)。 任何對reserve()的競爭訪問現在都會被阻止(因為互斥體被鎖定)。 當擁有資源的線程完成后,它只會銷毀監視器 object,從而解鎖資源。 (這允許將 RAII 應用於所有這些,從而使您的代碼安全且可維護。)

我修改了 OPs 代碼以勾勒出它的樣子:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

class information_t {

  private:
    std::mutex mtx;
    int importantValue = 0;

  public:
    class Monitor {
      private:
        information_t& resource;
        std::lock_guard<std::mutex> lock;
    
      friend class information_t; // to allow access to constructor.
      
      private:      
        Monitor(information_t& resource):
          resource(resource), lock(resource.mtx)
        { }
      public:
        ~Monitor()
        {
          std::cout << "Result: " << resource.importantValue << '\n';
          resource.reset();
        }
      
        Monitor(const Monitor&) = delete; // copying prohibited
        Monitor& operator=(const Monitor&) = delete; // copy assign prohibited
    
      public:
        // exposed resource API for monitor owner:
        void incrementIt() { resource.incrementIt(); }
        void decrementIt() { resource.decrementIt(); }
        void reset() { resource.reset(); }
    };
    friend class Monitor; // to allow access to private members
  
  public:
    Monitor aquire() { return Monitor(*this); }
    
  private:
    // These should only be callable from the thread that currently holds the mutex
    // Hence, they are private and accessible through a monitor instance only
    void incrementIt() { importantValue++; }
    void decrementIt() { importantValue--; }
    void reset() { importantValue = 0; }

} protectedResource; // We only have one instance of this that we need to work with

#if 0 // OBSOLETE
// Free the resource so other threads can reserve and use it
void release()
{
    protectedResource.reset();
    protectedResource.mtx.unlock(); // Will this work? Can I guarantee the mtx is locked?
}
#endif // 0

// Supposed to make sure no other thread can reserve or use it now anymore!
information_t::Monitor reserve()
{ 
  return protectedResource.aquire();
}

using MyResource = information_t::Monitor;

int main()
{
    std::thread threads[3];
    
    threads[0]
      = std::thread([]
        {
          MyResource protectedResource = reserve();
          protectedResource.incrementIt();
          protectedResource.incrementIt();
          // scope end releases protectedResource
        });

    threads[1]
      = std::thread([]
        {
          try {
            MyResource protectedResource = reserve();
            throw "Haha!";
            protectedResource.incrementIt();
            // scope end releases protectedResource
          } catch(...) { }
        });

    threads[2]
      = std::thread([]
        {
          MyResource protectedResource = reserve();
          protectedResource.decrementIt();
            // scope end releases protectedResource
        });

    for (auto& th : threads) th.join();

    return 0;
}

Output:

Result: 2
Result: -1
Result: 0

coliru 現場演示

當我知道它總是按順序調用保留和釋放時,是否可以保護這樣的資源?

沒有必要再擔心這個了。 正確的用法是燒在:

  • 要訪問資源,您需要一個監視器。
  • 如果您得到它,您就是該資源的唯一所有者。
  • 如果退出 scope(將監視器存儲為局部變量),監視器將被銷毀,因此鎖定的資源會自動釋放。

后者甚至會發生意外救助(在 MCVE 中throw "Haha;"; )。

此外,我將以下功能private

  • information_t::increment()
  • information_t::decrement()
  • information_t::reset()

因此,沒有未經授權的訪問是不可能的。 要正確使用它們,必須獲取一個information_t::Monitor實例。 它為那些可以在監視器所在的 scope 中使用的函數提供public包裝器,即僅由所有者線程使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM