簡體   English   中英

test_and_set 使線程死鎖

[英]test_and_set makes the thread deadlock

#include<bits/stdc++.h>
using namespace std;
int cnt=0;
int locked=0;
int test_and_set(int * lock){
    int temp=*lock;
    *lock=1;
    return temp;
}
void cntOnes(int t){
    while(test_and_set(&locked));

    for(int i=0;i<2000;i++){
        cnt++;
        cout<<t<<"  ->  "<<cnt<<endl;
    }
    locked=0;
}
int main(){
    thread t1(cntOnes,1);
    thread t2(cntOnes,2);
    t1.join();
    t2.join();
}

我使用 test_and_set 方法讓其他線程首先等待,線程 t1 可以中斷 while 循環,但即使在線程 t1 將值設置為 0 之后。線程 t2 繼續運行 while 循環,它不會中斷 while 循環。 應該做哪些改變?

首先, 為什么我不應該#include <bits/stdc++.h>?

不幸的是,除此之外,您的整個代碼都是一場偉大的大數據競賽。 如果不使用互斥鎖或原子來同步它們,您根本無法使用普通變量在線程之間進行通信。 在兩個不同的線程中同時讀寫一個普通變量是數據競爭的定義,C++ 標准說數據競爭會導致未定義的行為(= makes your program completely broken)。

可以go錯的東西很多:

  • 由於數據競爭規則,編譯器假設一個普通變量不會自發地改變值,除非被這個線程寫入。 因此它認為while(test_and_set(&locked)); ,如果第一次不成功,以后就永遠不會成功,還不如優化成死循環。 (或者由於無限循環也是 UB,它可能會被完全刪除。)

  • 編譯器同樣假設沒有其他線程正在查看我們修改的普通變量。 因此,例如,由於它觀察到運行cntOnes的線程最終不可避免地會設置locked = 0 ,因此它可以決定在 2000 步循環之前執行此操作。

  • 您的test_and_set是原子的。 即使加載和存儲本身是原子的和順序一致的(它們不是),操作也可以按如下方式交錯:

thread 1                               thread 2
========                               ========
temp = *lock; // temp = 0
                                       temp = *lock; // temp = 0
*lock = 1;
                                       *lock = 1;
return temp; // return 0               return temp; // return 0

現在兩個線程都認為他們持有鎖。 哦親愛的。

所以你不能用普通的int變量實現正確的“鎖定”。 看起來您正在嘗試重新發明std::mutex ,那么為什么不直接使用它呢?

#include <thread>
#include <iostream>
#include <mutex>

int cnt = 0;
std::mutex my_lock;

void cntOnes(int t) {
    my_lock.lock();
    for(int i=0; i<2000; i++) {
        cnt++;
        std::cout << t << "  ->  " << cnt << std::endl;
    }
    my_lock.unlock();
}

int main() {
    std::thread t1(cntOnes,1);
    std::thread t2(cntOnes,2);
    t1.join();
    t2.join();
}

或者更好的是,用std::scoped_lock做 RAII 風格:

void cntOnes(int t) {
    std::scoped_lock<std::mutex> guard(my_lock);
    for(int i=0; i<2000; i++) {
        cnt++;
        std::cout << t << "  ->  " << cnt << std::endl;
    }
}

這里guard的構造函數獲取互斥量,而析構函數釋放它。 在 C++17 之前,您可以改用std::lock_guard ,只要您只使用一個互斥量即可。

如果你堅持自己做測試和設置,那么你必須使用原子,例如std::atomic<bool>std::atomic_flag 這很難做到正確,特別是如果你想用任何比std::memory_order_seq_cst弱的東西優化 memory 排序; 它需要完全理解 C++ memory model 這非常抽象。 所以我會等到您可以舒適地使用互斥鎖后再嘗試這種方法。

也可以看看:

暫無
暫無

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

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