Re: [問題] 關於std::mutex的應用

看板C_and_CPP作者 (Cattuz)時間4年前 (2020/04/30 01:55), 編輯推噓1(101)
留言2則, 1人參與, 4年前最新討論串2/2 (看更多)
※ 引述《icetofux ()》之銘言: : 標題: [問題] 關於std::mutex的應用 : 時間: Mon Apr 27 22:26:40 2020 : : 開發平台(Platform): (Ex: Win10, Linux, ...) : Win10/Linux : 編譯器(Ex: GCC, clang, VC++...)+目標環境(跟開發平台不同的話需列出) : GCC : 額外使用到的函數庫(Library Used): (Ex: OpenGL, ...) : None : 問題(Question): : : 最近在使用C++11的std::thread,我已經知道若要在不同的thread存取同一個變數, : 必須使用mutex來做管理才能達到Thread-Safe。 : : 目前遇到的問題是,若我有多個不同的變數分別必須在多個不同的thread內存取,我 : 除了變數名稱外,還必須一一產生對應的mutex,我想建立下列這樣的樣板類別: : : template <class T> : class SharedVariable { : private: : std::mutex mtx; : T data; : public: : T Get(void) { : T data_cpy; : std::lock_guard<std::mutex> lck(mtx); : data_cpy = this->data; : return data_cpy; : } : void Set(const T data) { : std::lock_guard<std::mutex> lck(mtx); : this->data = data; : } : }; : : 在產生變數物件的同時,該物件也同時具有一個不用額外命名的mutex,並且當我 : 透過Get/Set存取變數時,也自動做好了上鎖、解鎖的功能。 : : SharedVariable<int> shared_int; : SharedVariable<std::string> shared_string; : SharedVariable<std::vector<double>> shared_vector; : : shared_int.Set(123); : int a = shared_int.Get(); : : 目前比較讓我有疑慮的是,在不同的thread內使用物件本身(如上例的shared_int、 : shared_string、shared_vector)是一個Thread-Safe的行為嗎?我不確定要如何判 : 斷,想請有經驗的先進指教。 想回的東西比較多,就單獨回一篇。 有錯還請務必指正。 thread-safe比較簡單的一種判斷方式是,當某個thread在中途被context switch掉 其他的thread看到的狀態到底是不是對的,如果是不對的, 你不能給他看,白話文講就是要鎖起來 不考慮OOE跟memory barrier,某個物件跟兩條thread的關係可以粗略分三種: 1.兩條都只有讀 2.一條讀另一條讀/寫 3.兩條都要做讀/寫 第1種case就例如我先建一個質數表給兩個thread查, 因為這個質數表的狀態是不會變的,這個時候是不用鎖的 第2種case的例子就有點像你開DMA buffer給某個硬體裝置寫, 你再去讀這個buffer做處理 這時候寫的那邊不用顧慮讀錯的問題,因為只有他寫 他看到的狀態一定是最新,只讀的那邊則要去確保他看到的是最新的狀態, 視處理的任務而定,也有可能要做到每次狀態變更都要能知道 所以通常是讀的那端會做polling,或是寫的打interrupt通知之類的手段 第3種才是最常見的,兩邊都要做讀寫,此時就會有race condition的問題 但這不表示說,你把讀寫都各加一道大鎖就會沒事 因為讀寫有時候會有相依性,這個相依性會導致你要像原推文steve大講的那樣 你要保證他的順序性,或是在寫之前,你要確保他讀到的data是最新狀態才會對 後面這個就是compare and swap在做的事情 最簡單的例子就是i++,因為你寫回去i的值跟i本來的值有關 也就是我那段code寫的那樣,你的Get跟Set中間因為有一個解鎖的空隙 在這個空隙裡,如果thread A有context switch,B 就有可能拿到lock 導致讀寫序變成: A讀->B讀->A寫->B寫 或是A讀->B讀->B寫->A寫 而不論是哪個順序,最後都會變成A或B的其中一條thread對i的影響被蓋掉 這是為什麼i++這種操作會弄成atomic的fetch_add的原因 因為你就是要把他的順序涵蓋進critical section裡他才會對 另外就是你這邊Get出去都是copy,這樣子雖然可以保證thread有各自的instance 但在Set的時候就會變成最終只有幾條thread的modify是有效的 而你就算是改用reference,因為物件的operation沒有鎖 這裡不加鎖就沒辦法保證正確性了 所以一般是不會像你這樣用在存取上加鎖的方式來處理多執行緒問題, 因為真的要做,你可能至少要把物件提供的operation都包一層鎖, 而這樣還不夠,因為還是會有上面提到的讀寫相依性的問題要解決 那如果要做到這個地步,還不如看情況在function內做針對性的加鎖來的省事XD 而與其去直面多thread同時讀寫多個物件這個難題 不如像love大跟kobe大講的那樣 把物件的寫權交給單個thread,然後其他thread透過concurrent queue 去下command給這個thread寫, 這樣子相當於是把第3種case給抽象成第2種case來做,會好處理很多 : : 謝謝。 : : 餵入的資料(Input): : None : 預期的正確結果(Expected Output): : None : 錯誤結果(Wrong Output): : None : 程式碼(Code):(請善用置底文網頁, 記得排版,禁止使用圖檔) : None : 補充說明(Supplement): : : : -- : ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 118.169.91.49 (臺灣) : ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1587997603.A.BCE.html : → loveme00835: 或者你應該思考的是: 為什麼多個執行緒都可以去更改 04/27 23:43 : → loveme00835: 同個物件的狀態, 而不是讓單個專責的執行緒來做呢? 04/27 23:43 : → loveme00835: 可以參考看看 active object pattern 把兩者先鬆綁 04/27 23:43 : → icetofux: 我的程式由9個thread構成,8個各自跑不同的流程,透過so 04/28 07:45 : → icetofux: cket與不同設備通訊,1個負責GUI,把8個設備的細部資料 04/28 07:45 : → icetofux: 顯示出來,每個設備約有60多種的細部資料。文中所提在th 04/28 07:45 : → icetofux: read間共用的變數就是設備傳輸至GUI的細部資料。 04/28 07:45 : → icetofux: 謝謝loveme00835,我剛剛google了active object pattern 04/28 09:01 : → icetofux: ,好像就是用來避免大量mutex的設計模式,雖然沒把握馬 04/28 09:01 : → icetofux: 上帶入應用,但是一個可以努力的方向。 04/28 09:01 : 推 eye5002003: 這情況不是用std::atomic比較適合嗎? 04/28 09:47 : 以我對atomic的認識,似乎只有幾種基本型態有提供atomic,我不是很確定像是string或是vector能不能使用。 : → Lipraxde: 一般不是保護 critical section 嗎?每個變數都給一個 04/28 11:56 : → Lipraxde: mute lock 好像比較少看到 04/28 11:56 : → Lipraxde: mute -> mutex 04/28 11:56 : → kobe8112: 如果改成各設備更新的資料丟到各自的queue中,UI更新的 04/28 11:59 : → kobe8112: Thread輪詢各queue是否有資料需更新呢? 04/28 12:00 : ※ 編輯: icetofux (111.71.40.212 臺灣), 04/28/2020 12:52:59 : → sarafciel: https://ideone.com/wHfCXi 拿你的模板寫了一小段 04/28 13:53 : → sarafciel: 應該很容易看出問題才是 04/28 13:54 : 推 steak5566: 可以請樓上大大可以解說一下為什麼嗎 04/28 22:55 : → Caesar08: sarafciel的strt_flag型態要改成atomic<bool> 04/29 13:37 對 這邊要寫atomic<bool>,我疏忽了QQ : → Caesar08: 另外,func與func2不相等,func保護的是整段過程 04/29 13:38 : → Caesar08: func2只保護每次sv_int的read write 04/29 13:38 : → Caesar08: 結果不一樣是正常的 04/29 13:38 : → Caesar08: 你的Get直接return data_cpy就好,不用先create再copy 04/29 13:40 : → Caesar08: 另外有支援C++ 17的話,用shared_mutex會比mutex好 04/29 13:41 : 推 steve1012: 不是有鎖住就好 還要看你想保護的東西是什麼 比如說你 04/29 14:04 : → steve1012: 各個function 之間有沒有什麼順序先後需要保證的 04/29 14:04 : → steve1012: muli threading 寫的越簡單通常越好 容易看出有沒有問 04/29 14:04 : → steve1012: 題 04/29 14:04 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 123.193.54.11 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1588182916.A.13A.html

04/30 07:16, 4年前 , 1F
謝謝你的範例跟說明,因為我的情況屬於第二種案例,所以
04/30 07:16, 1F

04/30 07:16, 4年前 , 2F
我的做法確實沒考慮到第三種案例的可能會發生的缺陷。
04/30 07:16, 2F
文章代碼(AID): #1UgR-44w (C_and_CPP)
文章代碼(AID): #1UgR-44w (C_and_CPP)