一、unique_lock的头文件
unique_lock是C++11中新增的一个互斥量锁的类型,定义在头文件
#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;
}