- 互斥锁:mutex
- 成员函数
- 构造函数
- lock()
- unlock()
- try_lock()
- 应用测试
- lock/unlock
- try_lock/unclok
- lock_guard
- 死锁
- 死锁测试
std::mutex 是C++中最基本的互斥量,std::mutex 对象提供了独占所有权的特性,即不支持递归地对std::mutex 对象上锁。
std::recursive_lock 则可以递归地对互斥量对象上锁。
std::mutex 不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
lock()调用线程将该互斥量锁住,线程调用该函数会发生以下3 种情况:
(a)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。
(b)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
(c)如果当前互斥量被当前调用线程锁住,则会产生死锁。
解锁,释放对互斥量的所有权。
try_lock()尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。
线程调用该函数会出现下面3 种情况:
如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock 释放互斥量。
(a)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock 释放互斥量。
(b)如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
(c)如果当前互斥量被当前调用线程锁住,则会产生死锁。
代码演示:
#include#include #include using namespace std; volatile int counter(0); // non-atomic counter //volatile 表示不进行优化 多次循环不把i保存在寄存器,每次从内存获取。 mutex mtx; // locks access to counter void increase10Ktime() { for (int i = 0; i < 10000; i++) { mtx.lock(); counter++; mtx.unlock(); } } int main() { thread ths[10]; for (int i = 0; i < 10; i++) { ths[i] = thread(increase10Ktime); } for (auto& th : ths) th.join(); cout << "after successful increase :" << counter << endl; return 0; }
运行结果:
如果使用互斥锁进行控制。
代码演示:
#include#include #include using namespace std; volatile int counter(0); // non-atomic counter //volatile 表示不进行优化 多次循环不把i保存在寄存器,每次从内存获取。 mutex mtx; // locks access to counter void increase10Ktime() { for (int i = 0; i < 10000; i++) { counter++; } } int main() { thread ths[10]; for (int i = 0; i < 10; i++) { ths[i] = thread(increase10Ktime); } for (auto& th : ths) th.join(); cout << "after successful increase :" << counter << endl; return 0; }
运行结果:
代码演示:
#include#include #include using namespace std; volatile int counter(0); // non-atomic counter mutex mtx; // locks access to counter void increase10Ktime() { for (int i = 0; i < 10000; i++) { while (!mtx.try_lock()); //没有锁死等 counter++; mtx.unlock(); } } int main() { thread ths[10]; for (int i = 0; i < 10; i++) { ths[i] = thread(increase10Ktime); } for (auto& th : ths) th.join(); cout << "after successful increase :" << counter << endl; return 0; }
运行结果:
在lock_guard 对象构造时,传入的Mutex 对象(即它所管理的Mutex 对象)会被当前线程锁住。
在lock_guard 对象被析构时,它所管理的Mutex 对象会自动解锁。
由于不需要程序员手动调用lock 和unlock 对Mutex 进行上锁和解锁 *** 作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的Mutex 对象可以正确进行解锁 *** 作,极大地简化了程序员编写与Mutex 相关的异常处理代码。
与Mutex RAII 相关,方便线程对互斥量上锁。
不使用lock_guard
代码演示:
#include#include #include using namespace std; mutex mtx; void printEven(int i) { if (i % 2 == 0) cout << i << " is even" << endl; else throw logic_error("not even"); //抛出异常 } void printThreadId(int id) { try { mtx.lock(); printEven(id); mtx.unlock(); } catch (logic_error&) { cout << "exception caught" << endl; } } int main() { thread ths[10]; //spawn 10 threads for (int i = 0; i < 10; i++) { ths[i] = thread(printThreadId, i + 1); } for (auto& th : ths) th.join(); return 0; }
运行结果:
抛出异常之后,就会出现抛出异常的线程只有加锁,没有释放锁的过程。
只要有一个线程没有释放,和这个锁相关的所有线程都会阻塞。
代码优化:
#include#include #include using namespace std; mutex mtx; void printEven(int i) { if (i % 2 == 0) cout << i << " is even" << endl; else throw logic_error("not even"); //抛出异常 } void printThreadId(int id) { try { //RAII: mtx资源获取即初始化。 lock_guard lck(mtx); //栈自旋抛出异常时栈对象自我析构。 printEven(id); } catch (logic_error&) { cout << "exception caught" << endl; } //离开当前作用于自动释放mtx锁。 } int main() { thread ths[10]; //spawn 10 threads for (int i = 0; i < 10; i++) { ths[i] = thread(printThreadId, i + 1); } for (auto& th : ths) th.join(); return 0; }
运行结果:
可以看到:当前线程抛出异常不影响其他线程。
死锁A拿到自己的锁没有释放,但是要去占用B的锁。B拿到自己的锁没有释放,但是要去占用A的锁。
A已经获取一把锁,又试图获取已获得的锁。
死锁测试死锁的原因:
container 试图多次去获取己获得的锁。std::recursive_mutex 允许多次获取相同的mutex。
C++中STL 中的容器,是非线程安全的。
代码演示:
#include#include #include #include #include #include #include using namespace std; template class container { public: void add(T element) { _mtx.lock(); //已经上锁 再次上锁 导致死锁 _elements.push_back(element); _mtx.unlock(); } void addrange(int num, ...) { va_list arguments; va_start(arguments, num); for (int i = 0; i < num; i++) { _mtx.lock();//上锁 add(va_arg(arguments, T)); _mtx.unlock(); } va_end(arguments); } void dump() { _mtx.lock(); for (auto e : _elements) cout << e << endl; _mtx.unlock(); } private: mutex _mtx; vector _elements; }; void func(container & cont) { cont.addrange(3, rand(), rand(), rand()); } int main() { srand((unsigned int)time(0)); container cont; thread t1(func, ref(cont)); thread t2(func, ref(cont)); thread t3(func, ref(cont)); t1.join(); t2.join(); t3.join(); cont.dump(); return 0; }
运行结果:
使用递归锁可以解决死锁:
代码演示:
#include#include #include #include #include #include #include using namespace std; template class container { public: void add(T element) { unique_lock lck(_mtx);//使用递归锁 _elements.push_back(element); _mtx.unlock(); } void addrange(int num, ...) { va_list arguments; va_start(arguments, num); for (int i = 0; i < num; i++) { unique_lock lck(_mtx);//使用递归锁 _mtx.lock();//上锁 add(va_arg(arguments, T)); } va_end(arguments); } void dump() { unique_lock lck(_mtx);//使用递归锁 for (auto e : _elements) cout << e << endl; } private: recursive_mutex _mtx; vector _elements; }; void func(container & cont) { cont.addrange(3, rand(), rand(), rand()); } int main() { srand((unsigned int)time(0)); container cont; thread t1(func, ref(cont)); thread t2(func, ref(cont)); thread t3(func, ref(cont)); t1.join(); t2.join(); t3.join(); cont.dump(); return 0; }
运行结果:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)