[英]C++11 Implementation of Spinlock using <atomic>
我實現了SpinLock類,如下所示
struct Node {
int number;
std::atomic_bool latch;
void add() {
lock();
number++;
unlock();
}
void lock() {
bool unlatched = false;
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire));
}
void unlock() {
latch.store(false , std::memory_order_release);
}
};
我實現了上面的類,並創建了兩個線程,每個線程調用一個相同的Node類實例的add()方法1000萬次。
不幸的是,結果不是2000萬。 我在這里錯過了什么?
問題是compare_exchange_weak
一旦失敗就會更新unlatched
變量。 從compare_exchange_weak
的文檔:
將原子對象包含的值的內容與預期值進行比較: - 如果為true,則用val替換包含的值(如store)。 - 如果為false,則將其替換為包含的值。
即第一失敗后compare_exchange_weak
, unlatched
將更新為true
,那么下一個循環周期將嘗試compare_exchange_weak
true
與true
。 這成功了,你只是拿了一個由另一個線程持有的鎖。
解決方案:確保設置unlatched
回false
每個前compare_exchange_weak
,如:
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire)) {
unlatched = false;
}
正如@gexicide所提到的,問題是compare_exchange
函數使用原子變量的當前值更新expected
變量。 這也是為什么你必須首先使用未unlatched
的局部變量的原因。 要解決此問題,您可以在每次循環迭代中將unlatched
設置為false。
但是,使用std::atomic_flag
來代替使用compare_exchange
,而不是使用compare_exchange
,它更適合使用std::atomic_flag
:
class SpinLock {
std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
void lock() {
while (locked.test_and_set(std::memory_order_acquire)) { ; }
}
void unlock() {
locked.clear(std::memory_order_release);
}
};
資料來源: cppreference
手動指定內存順序只是一個次要的潛在性能調整,我從源代碼復制。 如果簡單性比最后一點性能更重要,您可以堅持使用默認值並只調用locked.test_and_set() / locked.clear()
。
順便說一句: std::atomic_flag
是唯一保證無鎖的類型,雖然我不知道任何平台, std::atomic_bool
上的std::atomic_bool
不是免費的。
更新:正如@David Schwartz,@ Anton和@Technik Empire的評論中所解釋的那樣,空循環有一些不良影響,如分支未預測,HT處理器上的線程飢餓和過高的功耗 - 所以簡而言之,這是一個非常低效的等待的方式。 影響和解決方案是架構,平台和應用程序特定的。 我不是專家,但通常的解決方案似乎是在linux上添加cpu_relax()
或在循環體上添加窗口上的YieldProcessor()
。
EDIT2:為了清楚起見:這里提供的便攜版本(沒有特殊的cpu_relax等指令)應該足以滿足許多應用程序的要求。 如果你的SpinLock
旋轉很多,因為其他人持有鎖很長時間(這可能已經表明一般的設計問題),最好還是使用普通的互斥鎖。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.