C++11提供如下4种语义的互斥量(mutex)
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁
#include // std::cout
#include // std::mutex
#include // std::thread
volatile int counter(0); // non-atomic counter
std::mutex mtx; // locks access to counter
void increases_10k() {
for (int i = 0; i < 10000; ++i) {
// 1. 使用try_lock的情况
// if (mtx.try_lock()) {
// only increase if currently not locked: // ++counter; // mtx.unlock(); //
// } //
// 2. 使用lock的情况
{
mtx.lock();
++counter;
mtx.unlock();
}
}
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(increases_10k);
for (auto &th : threads)
th.join();
std::cout << " successful increases of the counter " << counter <<
std::endl; return 0;
}
递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
虽然递归锁能解决这种情况的死锁问题,但是尽量不要使用递归锁,主要原因如下:
不使用递归锁:
#include
#include
#include
struct Complex {
std::mutex mutex;
int i;
Complex() : i(0){}
void mul(int x) {
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
boid both(int x, int y) {
std::lock_guard<std::mutex> lock(mutex);
mul(x);
}
};
int main(void) {
Complex complex;
complex.both(32, 23);
return 0;
}
会导致死锁,因为在both中已经上了锁,还未释放锁便在mul中再次申请了锁。
使用递归锁:
#include
#include
#include
struct Complex {
std::recursive_mutex mutex;
int i;
Complex() : i(0){}
void mul(int x) {
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void both(int x, int y) {
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
}
};
int main(void) {
Complex complex;
complex.both(32, 23); //因为同一线程可以多次获取同一互斥量,不会发生死锁
std::cout << "main finish\n";
return 0;
}
#include
#include
#include
#include
std::timed_mutex mutex;
void work() {
std::chrono::milliseconds timeout(100);
while (true) {
if (mutex.try_lock_for(timeout)){
std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(250);
std::this_thread::sleep_for(sleepDuration);
mutex.unlock();
std::this_thread::sleep_for(sleepDuration);
}else {
std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(100);
std::this_thread::sleep_for(sleepDuration);
}
}
}
int main(void) {
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
std::cout << "main finish\n";
return 0;
}
都能获取到锁,一直打印“do work without the mutex”
使用lock_gurad和unique_lock有利于遵守RAII的规范。
#include
#include // std::thread
#include // std::mutex, std::lock_guard
#include // std::logic_error
std::mutex mtx;
void print_even (int x) {
if (x%2==0)
std::cout << x << " is even\n";
else
throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
try {
// using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
std::lock_guard<std::mutex> lck (mtx);
print_even(id);
}catch (std::logic_error&) {
std::cout << "[exception caught]\n";
}
}
int main () {
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads)
th.join();
return 0;
}
#include
#include
#include
#include
#include
#include
std::deque<int> q;
std::mutex mu;
std::condition_variable cond;
int count = 0;
void fun1() {
while (true) {
// {
std::unique_lock<std::mutex> locker(mu);
q.push_front(count++);
locker.unlock(); // 手动解锁或者加大括号
cond.notify_one();
// }
sleep(1);
}
}
void fun2() {
while (true) {
std::unique_lock<std::mutex> locker(mu);
cond.wait(locker, [](){return !q.empty();});
auto data = q.back();
q.pop_back();
// locker.unlock(); // 没有必要,unique_lock会自动解锁
std::cout << "thread2 get value form thread1: " << data << std::endl;
}
}
int main() {
std::thread t1(fun1);
std::thread t2(fun2);
t1.join();
t2.join();
return 0;
}
使用条件变量的目的就是为了,在没有获得某种提醒时长时间休眠; 如果正常情况下, 我们需要一直循环(+sleep), 这样的问题就是CPU消耗+时延问题,条件变量的意思是在cond.wait这里一直休眠直到cond.notify_one唤醒才开始执行下一句; 还有cond.notify_all()接口用于唤醒所有等待的线程。
那么为什么必须使用unique_lock呢?
原因: 条件变量在wait时会进行unlock再进入休眠, lock_guard并无该操作接口
需要结合notify+wait的场景使用unique_lock; 如果只是单纯的互斥使用lock_guard