一、unique_lock的頭文件
unique_lock是C++11中新增的一個互斥量鎖的類型,定義在頭文件中。unique_lock最主要的一個作用是實現線程安全的資源管理。unique_lock可以被移動但不能被複制,因為一個unique_lock保存了與某個mutex相關的關鍵信息。
#include <mutex>
std::unique_lock<std::mutex> lock(mutex);
unique_lock的構造函數接受一個mutex的引用,並嘗試使用這個mutex進行加鎖。如果mutex已經被其他線程鎖定,那麼這個線程會被阻塞直到這個鎖被釋放為止。下面是一個使用unique_lock的簡單示例:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx;
std::vector<int> vec;
void pushVec(int n)
{
std::unique_lock<std::mutex> lock(mtx); // unique_lock加鎖
vec.push_back(n); // 對共享資源進行操作
}
int main()
{
std::thread t1(pushVec, 1);
std::thread t2(pushVec, 2);
t1.join();
t2.join();
for(auto i : vec)
{
std::cout << i << " ";
}
return 0;
}
上面的示例將兩個數1和2分別加入到了std::vector中,並在主線程中通過迭代器遍歷輸出。
二、unique_lock詳解
1、unique_lock的基本使用
除了構造函數之外,unique_lock還提供了一些其他的使用方式。
- 可以使用unique_lock的lock()函數手動鎖定(或者解鎖)mutex。
- 當unique_lock的作用域結束時會自動釋放mutex。
- unique_lock還提供了與條件變數結合使用的功能,可以在wait()函數中自動解鎖mutex並等待條件變數的通知。
unique_lock在實現資源管理的方面非常有用。下面是一個使用unique_lock實現同步輸出的示例。這個示例中共有10個線程,每個線程循環執行10次,每次輸出自己的ID和一個隨機數字。我們要求它們一定按照ID遞增的順序依次輸出。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool flag = false;
const int threadCount = 10;
void printID(int id)
{
for(int i = 0; i < 10; ++i)
{
std::unique_lock<std::mutex> lock(mtx);
while(flag == false || (id == 0 && i == 0))
{
cv.wait(lock);
}
std::cout << id << " " << rand()%100 << std::endl;
flag = false;
cv.notify_all();
}
}
int main()
{
std::vector<std::thread> vec;
for(int i = 0; i < threadCount; ++i)
{
vec.emplace_back(printID, i);
}
flag = true;
cv.notify_all();
for(auto &t : vec)
{
t.join();
}
return 0;
}
這個示例中有10個線程,任意一個線程中的操作都不能影響到另一個線程。程序的實現方式是為每個線程定義一個id,然後每個線程循環執行10次,每次輸出自己的id和一個隨機數。要求每個線程都輸出完自己的內容後,主線程輸出全部內容。
為了保證線程按照id遞增的順序輸出,我們使用了一個flag和一個條件變數cv。flag=true表示線程可以輸出,false表示線程要等待其他線程的輸出完成後才能輸出。當線程輸出完自己的內容後,需要喚醒其他等待線程的輸出。這裡使用了cv.wait(lock)等待條件變數cv的通知,同時unique_lock的構造函數會鎖住mutex,確保線程的安全性。
2、unique_lock的高級使用
unique_lock除了上面提到的一些基本使用方法,還有許多高級的操作。例如:
- 可以將unique_lock的lock()函數作為可調用對象傳遞給線程,線程會在運行時執行lock()函數並加鎖。
- 可以將unique_lock的拷貝/移動構造函數直接傳遞給線程作為參數,線程會在運行時創建unique_lock實例並加鎖。
- 可以使用notfy_one()喚醒等待中的線程。
- 可以使用try_lock()函數嘗試加鎖,返回真表示加鎖成功,返回假表示加鎖失敗。
喚醒等待中的線程非常有用,省去了等待時間,可以節省CPU時間。下面是一個使用notfy_one()的示例:
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
std::vector<std::thread::id> vec;
bool flag = false;
void addID()
{
while(true)
{
std::unique_lock<std::mutex> lock(mtx);
if(flag == true)
{
std::cout << "Inserting ID: " << std::this_thread::get_id() << std::endl;
vec.push_back(std::this_thread::get_id());
flag = false;
cv.notify_one(); // 喚醒等待中的線程
}
else
{
cv.wait(lock);
}
}
}
int main()
{
std::vector<std::thread> tVec;
for(int i = 0; i < 3; ++i)
{
tVec.emplace_back(addID);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
flag = true;
cv.notify_all();
std::this_thread::sleep_for(std::chrono::seconds(1));
flag = true;
cv.notify_all();
std::this_thread::sleep_for(std::chrono::seconds(1));
flag = true;
cv.notify_all();
std::this_thread::sleep_for(std::chrono::seconds(1));
flag = true;
cv.notify_all();
for(auto &t : tVec)
{
t.join();
}
for(auto i : vec)
{
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
三、lock_guard
1、lock_guard的使用
lock_guard是std::mutex的一個RAII封裝。在構造函數中嘗試對mutex加鎖,在析構函數中自動解鎖。由於lock_guard只有一個有參構造函數並且沒有拷貝/移動構造函數,因此不能被拷貝和移動。lock_guard的使用方式非常簡單:
void func()
{
std::lock_guard<std::mutex> lock(mutex); // lock_guard加鎖
// 對共享資源進行操作
}
lock_guard在使用中非常容易理解且安全,讓我們可以輕鬆地使用互斥量而不用擔心忘記解鎖互斥量。
2、lock_guard和unique_lock的區別
lock_guard與unique_lock最大的區別就是在使用方式上。lock_guard的構造函數中只接受一個mutex的引用,並對mutex進行加鎖;在lock_guard的析構函數中會對mutex進行解鎖。unique_lock的構造函數同樣接受mutex的引用,但unique_lock的構造函數可以接受多個參數,比如try_to_lock()表示嘗試對mutex進行加鎖,如果失敗就直接返回;還有一個defer_lock()表示unique_lock不會立即對mutex進行加鎖,而是後續再加鎖。unique_lock的析構函數會解鎖mutex。
總的來說,lock_guard更加簡單直接,而unique_lock相對來說更加靈活
3、lock_guard的簡單示例
下面是一個使用lock_guard實現同步累加器的示例。這個示例中有10個線程,每個線程循環執行10次,每次加1。最後輸出累加器的值。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx;
int sum = 0;
void add()
{
for(int i = 0; i < 10; ++i)
{
std::lock_guard<std::mutex> lock(mtx); // lock_guard加鎖
sum++;
}
}
int main()
{
std::vector<std::thread> tVec;
for(int i = 0; i < 10; ++i)
{
tVec.emplace_back(add);
}
for(auto &t : tVec)
{
t.join();
}
std::cout << sum << std::endl;
return 0;
}
原創文章,作者:LIJEC,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/329985.html