系列文章目录
- C++ mutex in C++ Threading
- Overview
- 1.mutex in C++ Threading
- 2.如何使用 std::recursive_mutex 来避免死锁问题?
- 3.在C++中,除了互斥锁,还有哪些同步机制可以保护共享数据?
- 4.
std:recursive_timed_mutex
在实际应用中有哪些优势和局限性? - 5.std::recursive_timed_mutex 在多线程编程中如何与其他同步机制配合使用?
- 6.在使用
std:recursive_timed_mutex
时,有哪些常见的错误或陷阱需要避免? - 7.
std:recursivetimedmutex
在实际项目中有哪些常见的应用场景? - 8.如何避免在使用
std:recursivetimedmutex
时出现死锁问题? - 9.在使用 std::recursive_timed_mutex 时,如何确保线程安全地访问共享资源?
- 10.在使用 std::recursive_timed_mutex 时,如何进行有效的性能测试?
- 关于作者
Overview
1.mutex in C++ Threading
在 C++ 的多线程编程中,mutex
(互斥锁)是一种同步原语,用于保护共享数据不被多个线程同时访问,从而避免竞态条件和数据损坏。C++11 引入了标准库中的线程支持,包括多种类型的互斥锁。
1.1.主要的互斥锁类型
-
std::mutex:
最基本的互斥锁,用于保护共享数据。它不能被递归锁定。 -
std::recursive_mutex:
可以被同一个线程多次锁定,这在递归函数需要锁定互斥体时非常有用。 -
std::timed_mutex:
与std::mutex
类似,但它允许尝试锁定操作在超时后失败,而非无限期等待。 -
std::recursive_timed_mutex:
结合了std::recursive_mutex
和std::timed_mutex
的特性,可以递归锁定,并且支持超时。
1.2.如何使用 std::mutex
锁定互斥锁:
- std::lock_guard:创建时自动锁定,析构时自动释放,用于保护作用域内的代码块。
- std::unique_lock:更灵活的锁,可以手动锁定和解锁,支持延迟锁定和重复锁定,适用于需要条件变量的场景。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥锁
int shared_data = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // 锁定互斥锁
++shared_data; // 安全地修改共享数据
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Shared data: " << shared_data << std::endl; // 输出 2
return 0;
}
1.3.注意事项
- 死锁:确保在所有情况下都能避免死锁,例如,总是以相同的顺序获取多个互斥锁。
- 递归锁定:只有
std::recursive_mutex
可以递归锁定。 - 异常安全:使用
std::lock_guard
或std::unique_lock
可以保证即使在抛出异常时互斥锁也能被释放。 - 优先级反转:长时间持有锁的线程可能会阻塞高优先级线程,导致优先级反转问题。
- 资源争用:如果多个线程频繁争用同一互斥锁,可能会导致性能瓶颈。
1.4.使用 std::unique_lock 的高级用法
条件变量:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待条件变量
std::cout << "Thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
lock.unlock();
cv.notify_all();
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(print_id, i);
}
std::cout << "10 threads ready to race...\n";
go();
for (auto& th : threads) {
th.join();
}
return 0;
}
在这个示例中,std::unique_lock
用于条件变量 cv
,允许线程在 ready
条件成立之前挂起等待。
正确使用互斥锁对于编写安全且高效的多线程程序至关重要。
2.如何使用 std::recursive_mutex 来避免死锁问题?
std::recursive_mutex
是 C++ 标准库中的一个互斥锁类型,它允许同一个线程可以多次获得同一个锁,而不会产生死锁。这在递归函数或者需要在同一个线程中多次获取锁的情况下非常有用。
2.1.使用 std::recursive_mutex
的基本步骤
-
包含头文件:
包含mutex
头文件。 -
声明
std::recursive_mutex
:
创建一个std::recursive_mutex
对象。 -
锁定和解锁:
使用std::lock_guard
或std::unique_lock
来管理锁的生命周期。
2.2.示例代码
#include <iostream>
#include <thread>
#include <mutex>
#include <recursive_mutex>
std::recursive_mutex rw_mutex;
void recursiveFunction(int depth) {
std::lock_guard<std::recursive_mutex> lock(rw_mutex);
std::cout << "Locked at depth " << depth << std::endl;
if (depth > 0) {
recursiveFunction(depth - 1);
}
std::cout << "Unlocked at depth " << depth << std::endl;
}
int main() {
std::thread t1(recursiveFunction, 2);
std::thread t2(recursiveFunction, 2);
t1.join();
t2.join();
return 0;
}
2.3.如何使用 std::recursive_mutex
避免死锁
-
避免嵌套锁:
使用std::recursive_mutex
允许你在同一个线程中多次锁定同一个互斥锁,从而避免死锁。 -
一致的锁顺序:
确保所有线程获取多个锁时都按照相同的顺序来获取,这样可以避免循环等待条件。 -
使用
try_lock
或try_lock_for
:
使用try_lock
或try_lock_for
尝试获取锁,如果获取失败则可以释放已持有的锁,避免死锁。 -
使用超时机制:
使用std::unique_lock
与std::condition_variable
的超时等待功能,如果超时则释放锁。 -
避免在持有锁时调用未知的库函数:
确保在持有锁时不调用可能会获取其他锁的库函数,因为这可能导致不可预见的锁依赖关系。
2.4.示例:使用 try_lock
避免死锁
#include <iostream>
#include <thread>
#include <mutex>
#include <recursive_mutex>
#include <chrono>
std::recursive_mutex rw_mutex1, rw_mutex2;
void lock_both(std::recursive_mutex& m1, std::recursive_mutex& m2) {
std::lock_guard<std::recursive_mutex> lock1(m1);
std::lock_guard<std::recursive_mutex> lock2(m2);
// 安全地访问共享资源
}
void threadFunction(std::recursive_mutex& m1, std::recursive_mutex& m2) {
if (m1.try_lock()) {
if (m2.try_lock()) {
lock_both(m1, m2);
m2.unlock();
} else {
// 释放 m1 并重试
m1.unlock();
}
}
}
int main() {
std::recursive_mutex rw_mutex1, rw_mutex2;
std::thread t1(threadFunction, std::ref(rw_mutex1), std::ref(rw_mutex2));
std::thread t2(threadFunction, std::ref(rw_mutex2), std::ref(rw_mutex1));
t1.join();
t2.join();
return 0;
}
在这个示例中,try_lock
用于尝试获取锁,如果获取失败,则释放已持有的锁,避免死锁。
通过这些方法,std::recursive_mutex
可以帮助你在复杂的多线程环境中更安全地管理锁,从而避免死锁问题。
3.在C++中,除了互斥锁,还有哪些同步机制可以保护共享数据?
在 C++ 中,除了互斥锁(std::mutex
),还有多种同步机制可以保护共享数据,这些机制提供了不同的功能和使用场景:
-
原子操作(
std::atomic
):
提供了一种无锁的线程安全编程方式,适用于对单个数据点进行简单的读写操作。std::atomic<int> atomic_var(0); atomic_var.fetch_add(1, std::memory_order_relaxed);
-
读写锁(
std::shared_mutex
):
允许多个线程同时读取共享数据,但写入时需要独占访问。std::shared_mutex rw_mutex; std::shared_lock<std::shared_mutex> lock(rw_mutex);
-
条件变量(
std::condition_variable
):
用于线程间的同步,允许一个或多个线程挂起,直到被其他线程唤醒。std::mutex mtx; std::condition_variable cv; bool ready = false; cv.wait(mtx, [&](