簡體   English   中英

避免使用多個互斥鎖來保護類似的競爭狀況

[英]avoiding multiple mutex locks for protecting similar race conditions

假設我有一些代碼使我處於競爭狀態,例如

class foo
{
  some_data data;
public:
  void bar(some_type arg)
  {
    // may change data
  }
  // ...
};

在沒有保護的情況下使用foo::bar()不會是線程安全的,因為另一個線程可能會同時調用bar() 因此,可以說,更好的選擇是

class foo
{
  some_data data;
  std::mutex my_mutex;
  void unsafe_bar(some_type arg);
public
  void bar(some_type arg)
  {
     std::lock_guard<std::mutex> lock(my_mutex);
     unsafe_bar(arg);
  }
};

但是,假設有許多相似的類,其成員函數也存在同樣的問題

class foo1 { public: void bar(some_type arg); /*...*/ };
class foo2 { public: void bar(some_type arg); /*...*/ };
class foo3 { public: void bar(some_type arg); /*...*/ };
class foo4 { public: void bar(some_type arg); /*...*/ };
class foo5 { public: void bar(some_type arg); /*...*/ };

每個都在一個代碼塊中調用:

void work(some_type arg, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
  f1.bar(arg);
  f2.bar(arg);
  f3.bar(arg);
  f4.bar(arg);
  f5.bar(arg);
}

現在,每個調用都會鎖定和解鎖另一個互斥鎖,這似乎效率很低。 最好只使用一個互斥鎖。

問:是否有推薦/最佳方法?

我在考慮以下設計:

class foo
{
  std::mutex my_mutex;
  void unsafe_bar(some_type arg);
public:
  template<typename Mutex>
  void bar(some_type arg, Mutex&m)
  {
    std::lock_guard<Mutex> lock(m);
    unsafe_bar(arg);
  }
  void bar(some_type arg)
  {
    bar(arg,my_mutex);
  }
};

現在,用戶可以依靠foo::my_mutex (第二個版本的foo::bar ),或提供互斥鎖,可以是std::recursive_mutexnull_mutex (滿足互斥鎖概念,但不執行任何操作)。 :這是一個明智/有用的想法嗎?

編輯我注意到我的比賽實際上並不依賴於任何特定的迭代器(及其可能的無效性等)。 我刪除了對迭代器的任何引用。

tl; dr最終,如何在對象上設置線程同步取決於您自己,並且在從單獨的對象調用互斥lock的鎖時似乎效率不高,請在總體設計中考慮到這一點並弄清楚所需的“位置”您的互斥量/信號量和其他同步對象可以最好地處理與使代碼“線程安全”相關的開銷。 而且,如果您正在編寫將在外部使用的代碼(例如庫代碼),則需要確保您記錄特定功能實際上是“線程安全的”,否則我(作為用戶)將自己承擔該功能保護我自己的代碼。

void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
    f1.bar(i);
    f2.bar(i);
    f3.bar(i);
    f4.bar(i);
    f5.bar(i);
}

問:是否有推薦/最佳方法?

是的,將互斥鎖從foo對象中取出並放在其他位置;

// defined somewhere
std::mutex _mtx;

void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
    _mtx.lock();
    f1.bar(i);
    f2.bar(i);
    f3.bar(i);
    f4.bar(i);
    f5.bar(i);
    _mtx.unlock();
}

template<typename Mutex> void bar(some_iterator const&i, Mutex&m)是明智/有用的主意?

不,如上面的示例所示,調用“全局”互斥/ semphore對象要容易得多(也更簡潔); 如果我想使用您的模板化函數執行相同的示例,則將需要進行額外的鍵入,進行額外的調用,並且可能不會達到我想要的效果,例如:

// defined somewhere
std::mutex _mtx;

void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
    f1<std::mutex>.bar(i, _mtx);
    f2<std::mutex>.bar(i, _mtx);
    f3<std::mutex>.bar(i, _mtx);
    f4<std::mutex>.bar(i, _mtx);
    f5<std::mutex>.bar(i, _mtx);
}

我必須專門了解work函數中的互斥鎖,因為我的some_iterator可能會在工作函數之外進行修改(即使每個foo對象都有一個互斥鎖),例如:

void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
    // i could be modified here
    f1.bar(i); // here if mutex handle != others
    // here
    f2.bar(i); // here if mutex handle != others
    // here
    f3.bar(i); // here if mutex handle != others
    // here
    f4.bar(i); // here if mutex handle != others
    // here
    f5.bar(i); // here if mutex handle != others
    // here
}

? 當然,這可能就是您 如果work函數本質上不是設計成原子的,那么我(作為函數的用戶)可能在work函數周圍放置了互斥鎖,例如(假設上面的代碼):

std::mutex _mtx2;

void some_thread_fn1()
{
    _mtx2.lock();
    // some other code
    work(i, f1, f2, f3, f4, f5);
    _mtx2.unlock();
}

void some_thread_fn2()
{
    _mtx2.lock();
    // some other code
    work(i, f1, f2, f3, f4, f5);
    _mtx2.unlock();
}

曾經使用過work功能的人員將需要(某種程度上)知道其中調用了互斥鎖,否則他們可能會雙重保護(違背了您的初衷)。

還要注意,您的代碼實際上都不會鎖定相同的互斥鎖(假設foo類中也包含std::mutex ),例如:

class foo1
{
    some_data data;
    std::mutex my_mutex;
    void unsafe_bar(some_iterator const&i);
public:
    void bar(some_iterator const&i)
    {
        std::lock_guard<std::mutex> lock(my_mutex);
        unsafe_bar(i);
    }
};

class foo2
{
    some_data data;
    std::mutex my_mutex;
    void unsafe_bar(some_iterator const&i);
public:
    void bar(some_iterator const&i)
    {
        std::lock_guard<std::mutex> lock(my_mutex);
        unsafe_bar(i);
    }
};


foo1 f1;
foo2 f2;
some_iterator x;

void thread1()
{
    f1.bar(x);
}

void thread2()
{
    f2.bar(x);
}

上面的代碼會導致爭用情況,因為您要鎖定2個單獨的互斥鎖(邏輯錯誤,因為您想鎖定同一互斥鎖)。

正如另一個答案指出的那樣,這個問題似乎更多地是關於多線程設計及其最佳實踐的。

希望能對您有所幫助。

有推薦/最佳的方法嗎?

我認為您的做法很普遍,也是避免比賽狀況的好方法。

這是一個明智/有用的想法嗎?

我認為第二種方法值得懷疑。 您將把同步的負擔放在班級的客戶/用戶身上。 至少在使用模板化函數時。 但是,如果執行此操作,則可以只聲明類不是線程安全的,並且調用者必須在所有情況下都將其同步。 至少是一致的。

通常,您應該在數據上並發訪問的數據成員上進行同步。 互斥鎖的數量更多地與粒度有關。 例如,始終鎖定一個完整的塊或僅鎖定讀/寫操作。 但這取決於用例。

編輯 :由於您現在發布了一些代碼。
它仍然取決於您要如何使用您的類。
f1到f5僅在
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)方法。 然后,根本不要在它們中使用互斥體。 類的用戶很容易進行同步。 work的互斥。
這些類是否與工作方法分開使用?
同樣,答案將是保護與其自己的互斥鎖一起使用的每個類成員,因為對於客戶端而言,這將更加困難。

暫無
暫無

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

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