在C++多线程开发中,当多个线程需要操作同一个共享变量或资源时,如果没有同步机制,就会出现数据不一致的问题。mutex(互斥锁)和lock(锁机制)就是用来解决这类线程同步问题的核心组件,二者配合可以保证共享资源在同一时间只被一个线程访问。

mutex的基本概念
mutex是C++标准库提供的互斥量类型,定义在<mutex>头文件中,它的核心作用是实现线程间的互斥访问。当一个线程获取了mutex的所有权后,其他试图获取该mutex的线程会被阻塞,直到持有mutex的线程释放它。
C++标准库提供了多种mutex类型,常用的包括:
- std::mutex:最基本的互斥锁,不支持递归获取,不支持定时等待
- std::recursive_mutex:递归互斥锁,同一个线程可以多次获取该锁而不会死锁
- std::timed_mutex:支持定时等待的互斥锁,线程可以尝试在指定时间内获取锁
lock的作用与使用方式
lock并不是单独的类型,而是指获取和释放互斥锁的操作过程,C++标准库提供了多种管理锁的方式,避免手动操作mutex时出现忘记释放锁的问题。
手动lock和unlock
std::mutex提供了lock()和unlock()两个成员函数,分别用于获取锁和释放锁,使用时必须保证获取锁之后一定要释放锁,否则会导致其他线程永久阻塞。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_counter = 0;
void increment_counter(int thread_id) {
// 获取互斥锁
g_mutex.lock();
// 临界区:操作共享资源
for (int i = 0; i < 1000; ++i) {
++g_counter;
}
std::cout << "线程" << thread_id << "完成计数,当前值:" << g_counter << std::endl;
// 释放互斥锁
g_mutex.unlock();
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_counter, i);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数结果:" << g_counter << std::endl;
return 0;
}
使用std::lock_guard自动管理锁
手动调用lock和unlock容易遗漏unlock操作,尤其是在临界区抛出异常的场景下,因此更推荐使用RAII风格的锁管理类型,std::lock_guard就是最常用的一种。它会在构造时自动获取锁,析构时自动释放锁,即使临界区抛出异常也能保证锁被正确释放。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_counter = 0;
void increment_counter(int thread_id) {
// 构造时自动获取锁
std::lock_guard<std::mutex> lock(g_mutex);
// 临界区:操作共享资源
for (int i = 0; i < 1000; ++i) {
++g_counter;
}
std::cout << "线程" << thread_id << "完成计数,当前值:" << g_counter << std::endl;
// 函数结束时lock对象析构,自动释放锁
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_counter, i);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数结果:" << g_counter << std::endl;
return 0;
}
使用std::unique_lock更灵活管理锁
std::unique_lock比std::lock_guard更灵活,它支持延迟获取锁、手动释放锁、所有权转移等操作,适合需要更精细控制锁生命周期的场景。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex g_mutex;
int g_counter = 0;
void increment_counter(int thread_id) {
// 延迟获取锁,先构造unique_lock再手动调用lock
std::unique_lock<std::mutex> lock(g_mutex, std::defer_lock);
// 执行一些不需要锁的操作
std::cout << "线程" << thread_id << "准备获取锁" << std::endl;
// 手动获取锁
lock.lock();
// 临界区操作
for (int i = 0; i < 1000; ++i) {
++g_counter;
}
std::cout << "线程" << thread_id << "完成计数,当前值:" << g_counter << std::endl;
// 可以手动提前释放锁
lock.unlock();
std::cout << "线程" << thread_id << "已释放锁" << std::endl;
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_counter, i);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最终计数结果:" << g_counter << std::endl;
return 0;
}
mutex和lock的使用注意事项
- 不要重复对同一个std::mutex调用lock(),否则会导致未定义行为,递归场景应使用std::recursive_mutex
- 尽量缩小临界区的范围,只把操作共享资源的代码放在锁的保护范围内,减少锁的持有时间
- 避免多个线程按不同顺序获取多个锁,否则容易出现死锁问题,可以使用std::lock同时获取多个锁
- 优先使用RAII风格的锁管理类型(lock_guard、unique_lock),避免手动调用lock和unlock
总结
mutex是C++中实现线程互斥的基础组件,而lock是获取和释放互斥锁的操作过程,二者配合可以有效解决多线程下的数据竞争问题。实际开发中推荐使用std::lock_guard或std::unique_lock来管理锁,既安全又便捷。合理设计锁的使用范围和获取顺序,能够写出更稳定高效的多线程程序。