淺談c++11線程的互斥量
為什麼需要互斥量
在多任務操作系統中,同時運行的多個任務可能都需要使用同一種資源。這個過程有點類似於,公司部門裡,我在使用著打印機打印東西的同時(還沒有打印完),別人剛好也在此刻使用打印機打印東西,如果不做任何處理的話,打印出來的東西肯定是錯亂的。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> // 打印機 void printer(const char *str) { while(*str != '\0') { std::cout << *str; str++; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } std::cout << std::endl; } // 線程一 void func1() { const char *str = "hello"; printer(str); } // 線程二 void func2() { const char *str = "world"; printer(str); } void mytest() { std::thread t1(func1); std::thread t2(func2); t1.join(); t2.join(); return; } int main() { mytest(); system("pause"); return 0; }
獨占互斥量std::mutex
互斥量的基本接口很相似,一般用法是通過lock()方法來阻塞線程,直到獲得互斥量的所有權為止。在線程獲得互斥量並完成任務之後,就必須使用unlock()來解除對互斥量的占用,lock()和unlock()必須成對出現。try_lock()嘗試鎖定互斥量,如果成功則返回true, 如果失敗則返回false,它是非阻塞的。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> #include <mutex> std::mutex g_lock; //全局互斥鎖對象,#include <mutex> // 打印機 void printer(const char *str) { g_lock.lock(); //上鎖 while(*str != '\0') { std::cout << *str; str++; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } std::cout << std::endl; g_lock.unlock(); // 解鎖 } // 線程一 void func1() { const char *str = "hello"; printer(str); } // 線程二 void func2() { const char *str = "world"; printer(str); } void mytest() { std::thread t1(func1); std::thread t2(func2); t1.join(); t2.join(); return; } int main() { mytest(); system("pause"); return 0; }
使用std::lock_guard可以簡化lock/unlock的寫法,同時也更安全,因為lock_guard在構造時會自動鎖定互斥量,而在退出作用域後進行析構時就會自動解鎖,從而避免忘瞭unlock操作。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> #include <mutex> std::mutex g_lock; //全局互斥鎖對象,#include <mutex> // 打印機 void printer(const char *str) { std::lock_guard<std::mutex> locker(g_lock); // lock_guard 上鎖 while(*str != '\0') { std::cout << *str; str++; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } std::cout << std::endl; // 即將推出作用域 lock_guard 會自動解鎖 } // 線程一 void func1() { const char *str = "hello"; printer(str); } // 線程二 void func2() { const char *str = "world"; printer(str); } void mytest() { std::thread t1(func1); std::thread t2(func2); t1.join(); t2.join(); return; } int main() { mytest(); system("pause"); return 0; }
原子操作
所謂的原子操作,取的就是“原子是最小的、不可分割的最小個體”的意義,它表示在多個線程訪問同一個全局資源的時候,能夠確保所有其他的線程都不在同一時間內訪問相同的資源。也就是他確保瞭在同一時刻隻有唯一的線程對這個資源進行訪問。這有點類似互斥對象對共享資源的訪問的保護,但是原子操作更加接近底層,因而效率更高。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> //全局的結果數據 long total = 0; //點擊函數 void func() { for(int i = 0; i < 1000000; ++i) { // 對全局數據進行無鎖訪問 total += 1; } } void mytest() { clock_t start = clock(); // 計時開始 //線程 std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); clock_t end = clock(); // 計時結束 std::cout << "total = " << total << std::endl; std::cout << "time = " << end-start << " ms" << std::endl; return; } int main() { mytest(); system("pause"); return 0; }
由於線程間對數據的競爭而導致每次運行的結果都不一樣。因此,為瞭防止數據競爭問題,我們需要對total進行原子操作。
通過互斥鎖進行原子操作:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> #include <mutex> std::mutex g_lock; //全局的結果數據 long total = 0; //點擊函數 void func() { for(int i = 0; i < 1000000; ++i) { g_lock.lock(); // 加鎖 total += 1; g_lock.unlock(); // 加鎖 } } void mytest() { clock_t start = clock(); // 計時開始 //線程 std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); clock_t end = clock(); // 計時結束 std::cout << "total = " << total << std::endl; std::cout << "time = " << end-start << " ms" << std::endl; return; } int main() { mytest(); system("pause"); return 0; }
每次運行的結果都一樣,隻是耗時長點。
在新標準C++11,引入瞭原子操作的概念。
如果我們在多個線程中對這些類型的共享資源進行操作,編譯器將保證這些操作都是原子性的,也就是說,確保任意時刻隻有一個線程對這個資源進行訪問,編譯器將保證多個線程訪問這個共享資源的正確性。從而避免瞭鎖的使用,提高瞭效率。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread> #include <atomic> //原子數據類型 std::atomic<long> total(0); //需要頭文件 #include <atomic> //點擊函數 void func() { for(int i = 0; i < 1000000; ++i) { // total += 1; } } void mytest() { clock_t start = clock(); // 計時開始 //線程 std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); clock_t end = clock(); // 計時結束 std::cout << "total = " << total << std::endl; std::cout << "time = " << end-start << " ms" << std::endl; return; } int main() { mytest(); system("pause"); return 0; }
原子操作的實現跟普通數據類型類似,但是它能夠在保證結果正確的前提下,提供比mutex等鎖機制更好的性能。
以上就是淺談c++11線程的互斥量的詳細內容,更多關於c++11線程的互斥量的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C++11各種鎖的具體使用
- 詳解C語言編程之thread多線程
- C++詳細講解互斥量與lock_guard類模板及死鎖
- c++11 chrono全面解析(最高可達納秒級別的精度)
- C++ 對多線程/並發的支持(上)