競爭狀態與臨界區
- 1,基本互斥鎖
- 2,try_lock
- 3,互斥鎖存在的坑—線程搶占不到資源
- 4,超時鎖
- 5,遞歸鎖(在一個線程內可以多次lock的鎖)recursive_mutex和recursive_timed_mutex用于業務組合
- 6,利用棧特性自動釋放鎖RALL
- 6.1手動實現簡易RALL
- 6.2 c++11支持的RALL管理互斥資源 lock_guard
- 6.3 c++11 unique_lock c++11
1,基本互斥鎖
//互斥鎖使用
#include<mutex>
static mutex mux;
mux.lock();
mux.unlock();
案例
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void TestThread1()
{mux.lock();cout << "=====================================" << endl;cout << "test 001" << endl;cout << "test 002" << endl;cout << "test 003" << endl;cout << "=====================================" << endl;mux.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(50));
}void deal1()
{thread th(TestThread1);th.join();
}int main()
{deal1();
}
運行結果
互斥鎖的作用在于可以盡可能的避免數據競爭,在lock與unlock之間的區域被稱之為臨界區,要注意一點的是,臨界區要盡量小,鎖要盡早的申請且盡早的釋放。
2,try_lock
if (!mux.try_lock()){}
//嘗試去鎖,成功返回true,失敗返回false
案例:
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void TestThread1()
{for (;;){if (!mux.try_lock()){cout << "." << "flush" << endl;;this_thread::sleep_for(std::chrono::milliseconds(50));continue;}//mux.lock();cout << "=====================================" << endl;cout << "test 001" << endl;cout << "test 002" << endl;cout << "test 003" << endl;cout << "=====================================" << endl;mux.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(50));}}void deal1()
{for (int i = 0;i<10;i++){thread th1(TestThread1);th1.detach();}getchar();
}
int main()
{deal1();
}
注意,try_lock有性能開銷。
3,互斥鎖存在的坑—線程搶占不到資源
在2節中,unlock之后選擇讓線程睡眠50毫秒,為什么要要sleep 50毫秒呢???這就涉及到多線程并發時因多線程競爭cpu資源而容易出現的坑。
線程有這樣幾種狀態,
1,初始化(Init): 表示該線程正在創建。
2,就緒(Ready):表示該線程在就緒列表中,等待CPU調度。
3,運行(Running): 表示該線程正在運行。
4,阻塞(Blocked):該線程被阻塞掛起,包括,鎖,事件,信號量阻塞。此狀態不占用cpu資源
5,退出:該線程結束,等待父線程回收資源。
一個線程在創建,就緒之后,肯直接進入阻塞狀態,也可能先進入運行狀態,后進入阻塞狀態。當由運行狀態進入阻塞狀態之后,線程在阻塞一段時間之后可能因為等待互斥鎖或條件變量,重新進入就緒態,等待cpu調度之后,才再次進入運行狀態。
實際上,多線程并發運行,在某一個時間段之內其實只有一個線程在占用cpu資源,當某一個線程調用unlock之后,所有線程會再次去爭奪cpu資源,此時會出現一個神奇的現象,那就是,某一個線程在unlock之后,cpu資源被釋放,然后多個線程再次競爭cpu資源的時候,上一個占用cpu資源的線程再次獲取到了cpu資源,導致某一個線程長期能搶占到cpu資源而其他線程就無法順利運行下去,這種情況是我們在實際的項目當中不想遇到的,于是乎,在調用unlock之后,讓現場 sleep 一段事件,這樣就可以讓其他線程爭奪cpu資源,就不會出現某一個線程長期占用cpu資源的場景了。
4,超時鎖
static timed_mutex tmux;
if (!!tmux.try_lock_for(chrono::milliseconds(500))){}
//嘗試獲取鎖,當五百毫秒內沒有獲取到鎖資源,就會返回false
案例:
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static timed_mutex tmux;void ThewadMain(int i)
{for (;;){if (!tmux.try_lock_for(chrono::milliseconds(500))){cout << i << " lock failed " << endl;continue;}else{cout << i << " [in]" << endl;tmux.unlock();std::this_thread::sleep_for(chrono::microseconds(2000));}}
}void deal1()
{for (int i = 0; i < 3; i++){thread th(ThewadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
運行結果:
5,遞歸鎖(在一個線程內可以多次lock的鎖)recursive_mutex和recursive_timed_mutex用于業務組合
在我們開發某個項目的時候,隨著時間的推移,項目代碼越來越多,接口越來越多,會出現大量的接口層層調用的情況,肯會出現的一個情況是,某個外部接口已經對某個鎖進行了 lock 但是調用的兩一個接口也對某一個鎖進行了lock ,同一個鎖在同一個線程內連續調用兩次lock會發生什么現象呢?
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void Task1()
{mux.lock();cout << "task1" << endl;mux.unlock();
}void Task2()
{mux.lock();cout << "task2" << endl;mux.unlock();
}void ThewadMain(int i)
{mux.lock();Task1();Task2();mux.unlock();
}void deal1()
{thread th1(ThewadMain,1);
}int main()
{deal1();getchar();
}
我們發現,程序直接崩潰無法運行。
為了解決這個問題我們引入了遞歸鎖,所謂的遞歸鎖就是可以在一個線程中鎖多次。
static recursive_mutex rmux;
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>using namespace std;
static recursive_mutex rmux;void Task1()
{rmux.lock();cout << "task1" << endl;rmux.unlock();
}void Task2()
{rmux.lock();cout << "task2" << endl;rmux.unlock();
}void ThewadMain(int i)
{rmux.lock();Task1();Task2();rmux.unlock();
}void deal1()
{thread th1(ThewadMain,1);th1.detach();
}int main()
{deal1();getchar();
}
遞歸鎖可以多次lock,但是也要相應的進行多次unlock,比如我lock兩次,那么我也要相應的unlock兩次。
6,利用棧特性自動釋放鎖RALL
RALL c++之父提出,使用局部對象來管理資源的技術成為資源獲取即初始化,它的生命周期是由操作系統來管理的,無需人工介入;資源的銷毀容易忘記,造成死鎖或內存泄露。
簡單來說就是,我們自己管理鎖需要手動 lock 或者 手動 unlock ,但是隨著項目周期的拉長我們很可能忘記 unlock 造成死鎖,此時我們用一種方法可以不用手動實現 lock unlock 即可實現自動釋放,盡可能的避免死鎖或內存泄露。
6.1手動實現簡易RALL
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>using namespace std;
static mutex rmux;class XMutex
{
public:XMutex(mutex& mux):mux_(mux){mux_.lock();}~XMutex(){mux_.unlock();}
private:mutex& mux_;
};void ThreadMain(int i)
{XMutex lock(rmux);cout << i<<" in thread" << endl;
}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
6.2 c++11支持的RALL管理互斥資源 lock_guard
// A code block
static mutex rmux;
lock_guard<mutex> lock(rmux);
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex rmux;void ThreadMain(int i)
{{lock_guard<mutex> lock(rmux);cout << i << " in thread" << endl;}std::this_thread::sleep_for(chrono::microseconds(200));}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
如上述代碼所示,當出了大括號的范圍,自動調用析構函數,會自動進行unlock,。
6.3 c++11 unique_lock c++11
上一個小結的 lock_guard 是一個最基本的簡單的 RALL 鎖,但實際的項目開發過程會出現更復雜的業務需求,比如會存在 一個鎖賦值給另一個鎖,一個鎖移動到另一個鎖,或者可能會出現臨時手動解鎖加鎖,或者其他更復雜的需求,此時我們就會使用 unique_lock 。
// A code block
unique_lock c++11
支持臨時釋放鎖 unlock
支持 adopt_lock (已經擁有鎖,不加鎖,出棧區會釋放)
支持 defer_lock (延后擁有,不加鎖,出棧區不釋放)
支持 try_to_lock 嘗試獲得互斥的所有權而不阻塞,獲取失敗推出棧區不會釋放,通過 owns_lock() 函數判斷。
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex rmux;void ThreadMain(int i)
{{unique_lock<mutex> lock(rmux);cout << i << " in thread" << endl;}std::this_thread::sleep_for(chrono::microseconds(200));}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
支持臨時釋放鎖
// An highlighted block
void ThreadMain(int i)
{{unique_lock<mutex> lock(rmux);lock.unlock();cout << i << " in thread" << endl;lock.lock();}std::this_thread::sleep_for(chrono::microseconds(200));
}