std::mutex
mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。mutex 提供排他性非递归所有权语义。
操作:
1.lock:如果 mutex 未上锁,则将其上锁。否则如果已经其它线程 lock,则阻塞当前线程
2.try_lock:如果 mutex 未上锁,则将其上锁。否则返回 false,并不阻塞当前线程
3.unlock:如果 mutex 被当前线程锁住,则将其解锁。否则,是未定义的行为
4.native_handle:返回底层实现定义的线程句柄
例子:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;
int g_num = 0;//为 g_num_mutex 所保护
std::mutex g_num_mutex;
void slow_increment(int id) {
for(int i = 0; i < 3; ++i) {
g_num_mutex.lock();
++g_num;
cout << id << " => " << g_num << endl;
g_num_mutex.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main(void) {
std::thread t1(slow_increment, 0);
std::thread t2(slow_increment, 1);
t1.join();
t2.join();
// 输出:
// 0 => 1
// 1 => 2
// 0 => 3
// 1 => 4
// 0 => 5
// 1 => 6
return 0;
}
std::lock_guard
std::lock_guard是RAII模板类的简单实现,功能简单。
1.std::lock_guard 在构造函数中进行加锁,析构函数中进行解锁。
2.锁在多线程编程中,使用较多,因此c++11提供了lock_guard模板类;在实际编程中,我们也可以根据自己的场景编写resource_guard RAII类,避免忘掉释放资源。
下面是一个使用std::lock_guard的代码例子,1+2+ … + 100的多线程实现,每个num只能由一个线程处理:
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
#include <algorithm>
std::mutex my_lock;
void add(int &num, int &sum){
while(true){
std::lock_guard<std::mutex> lock(my_lock);
if (num < 100){ //运行条件
num += 1;
sum += num;
}
else { //退出条件
break;
}
}
}
int main(){
int sum = 0;
int num = 0;
std::vector<std::thread> ver; //保存线程的vector
for(int i = 0; i < 20; ++i){
std::thread t = std::thread(add, std::ref(num), std::ref(sum));
ver.emplace_back(std::move(t)); //保存线程
}
std::for_each(ver.begin(), ver.end(), std::mem_fn(&std::thread::join)); //join
std::cout << sum << std::endl;
}
std::unique_lock
类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
unique_lock比lock_guard使用更加灵活,功能更加强大。
使用unique_lock需要付出更多的时间、性能成本。
下面是try_lock的使用例子。
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <vector>
std::mutex mtx; // mutex for critical section
std::once_flag flag;
void print_block (int n, char c) {
//unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态
std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock);
//尝试加锁, 如果加锁成功则执行
//(适合定时执行一个job的场景, 一个线程执行就可以, 可以用更新时间戳辅助)
if(my_lock.try_lock()){
for (int i=0; i<n; ++i)
std::cout << c;
std::cout << '\n';
}
}
void run_one(int &n){
std::call_once(flag, [&n]{n=n+1;}); //只执行一次, 适合延迟加载; 多线程static变量情况
}
int main () {
std::vector<std::thread> ver;
int num = 0;
for (auto i = 0; i < 10; ++i){
ver.emplace_back(print_block,50,'*');
ver.emplace_back(run_one, std::ref(num));
}
for (auto &t : ver){
t.join();
}
std::cout << num << std::endl;
return 0;
}
死锁
std::mutex 的成员函数
构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
#include <iostream>
#include <mutex>
class myTestMutex
{
public:
myTestMutex(int m_m) : m(m_m) {}
~myTestMutex() {}
void Add(int number)
{
m_mutex.lock();
m += number;
m_mutex.unlock();
}
void PlusOne()
{
m_mutex.lock();
Add(1);
m_mutex.unlock();
}
private:
std::mutex m_mutex;
int m;
};
int main()
{
myTestMutex test(0);
//调用Add方法是没有问题的,而且有原子操作的效果。
test.Add(10);
//但是一定调用PlusOne方法就出现问题了,因为进入PlusOne方法的时候会调用m_mutex.lock()方法,
//还没有调用m_mutex.unlock()方法就马上进入Add方法了,然后Add方法马上又调用了m_mutex.lock(),
//然后m_mutex.lock()因为前面在PlusOne方法调用m_mutex.lock()方法,所以就出现阻塞,然后在Add方法就走不下去了,
//然后因为PlusOne方法调用了Add方法,所以导致PlusOne方法也走不下去了,出现死锁了。
test.PlusOne();
}