C++11 并发指南之std::mutex详解
什么是std::mutex?
std::mutex
是C++11标准中一个用于保护共享数据的同步原语。它是一个轻量级的锁,可以用于实现临界段或者锁保护的互斥访问。当一个线程执行到std::mutex
的lock()方法时,如果此前该锁已经被另一个线程占用,那么该线程会被挂起,直到该锁被释放为止。
std::mutex 如何使用?
std::mutex
非常易用,只需要在需要进行互斥访问的代码段前调用std::mutex
的lock()
方法,代码段执行完毕后再使用unlock()
方法释放该锁就可以了。例如:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex m;
int i = 0;
void f() {
m.lock();
i++; // 对共享数据进行修改
m.unlock();
}
int main() {
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << i << std::endl; // 可能打印的结果为2
return 0;
}
在上述代码中,两个线程共同修改了一个全局变量i,由于i是共享数据,因此需要使用std::mutex
来进行保护。在线程执行到修改i的代码前先调用m.lock()
方法对该锁进行加锁,修改完i后再使用m.unlock()
方法释放该锁。
另外,还可以使用C++11提供的std::lock_guard一次性加锁和解锁,代码会更简洁:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex m;
int i = 0;
void f() {
std::lock_guard<std::mutex> lk(m); // 自动加锁和解锁
i++; // 对共享数据进行修改
}
int main() {
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << i << std::endl; // 可能打印的结果为2
return 0;
}
使用std::lock_guard可以避免手动加锁解锁的问题,更优雅。
std::mutex的注意事项
-
一定要记得加锁和解锁;
-
尽量不要在锁住的代码段中调用其他可能会阻塞的代码,否则容易引起死锁;
-
尽量使用std::lock_guard等RAII技术来管理锁,避免手工开闭锁;
-
避免线程占用时间过长,在锁住的代码段中尽快完成临界区的操作。
示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex m1, m2;
int i = 0;
void g() {
std::lock_guard<std::mutex> lk1(m1); // 自动加锁和解锁
std::lock_guard<std::mutex> lk2(m2); // 自动加锁和解锁
i++; // 对共享数据进行修改
}
void h() {
std::lock_guard<std::mutex> lk2(m2); // 自动加锁和解锁
std::lock_guard<std::mutex> lk1(m1); // 自动加锁和解锁
i++; // 对共享数据进行修改
}
int main() {
std::thread t1(g);
std::thread t2(h);
t1.join();
t2.join();
std::cout << i << std::endl; // 可能打印的结果为2
return 0;
}
在这个示例中,两个线程都试图获取m1和m2两个锁,但是获取锁的顺序不同,会出现死锁的情况。如果在锁住的代码段中调用了可能会阻塞的代码,也同样容易产生死锁问题。
总结
std::mutex
是C++11中用于实现线程同步的一个重要内容。它是一个轻量级且易用的互斥锁,可用于任何需要保护共享数据的临界段。使用std::mutex
需要注意以下几点:加锁和解锁不能忘记,尽量使用RAII技术来保护锁,避免产生死锁问题,避免阻塞操作。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++11 并发指南之std::mutex详解 - Python技术站