编程C++ 多线程/服务器开发者

在使用 C++ 进行多线程时需要考虑哪些细节?请描述 std::mutex、std::lock_guard、std::unique_lock 之间的区别,并举例说明线程安全的工作方式。

用 Hintsage AI 助手通过面试

答案

在现代 C++(自 C++11 起)中,有丰富的多线程工具可供使用:std::thread、互斥量和各种包装对象用于管理资源访问。线程之间安全交互需要理解以下组件:

  • std::mutex — 用于同步访问的主要锁定原语。需要显式的锁定/解锁。
  • std::lock_guard — 一个 RAII 包装类,在进入/离开作用域时自动捕获和释放互斥量。
  • std::unique_lock — 更通用的 RAII 包装类,允许手动锁定/解锁,并在线程之间移动互斥量的所有权,使用 condition_variable。

为了避免竞争,始终在可能的情况下使用 RAII 包装类。请注意,尝试重复捕获同一互斥量会导致死锁(除非这是递归互斥量)。

正确工作的示例:

#include <iostream> #include <mutex> #include <thread> std::mutex mtx; void printThreadSafe(int id) { std::lock_guard<std::mutex> lock(mtx); std::cout << "Thread " << id << std::endl; } int main() { std::thread t1(printThreadSafe, 1); std::thread t2(printThreadSafe, 2); t1.join(); t2.join(); }

误导性问题

是否可以使用 std::lock_guardstd::mutex 来实现与 std::condition_variable 的条件同步?

回答: 不可以!使用 std::condition_variable 时必须使用 std::unique_lockstd::mutex,因为 lock_guard 不提供控制锁定的方法(例如,暂时释放 — unlock — 然后再重新捕获),而这在等待通知时是必需的。

示例:

std::mutex mtx; std::condition_variable cv; bool ready = false; void waitForEvent() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{return ready;}); // 后续处理 }

由于对主题细节不了解而导致的真实错误示例


故事

项目中手动使用 mtx.lock()/mtx.unlock()。在 lock/unlock 之间抛出异常时,互斥量保持锁定状态,多个线程“挂起”。更换为 RAII(std::lock_guard)后,问题消失。


故事

通过 std::condition_variable 通知时使用 std::lock_guard 而不是 std::unique_lock。结果,程序在新编译器上无法编译,在旧编译器上则由于 API 不一致而发生运行时断言。


故事

在代码中调用 std::lock_guard 的 unlock() 以进行“手动”控制对互斥量的访问。这导致在不同编译器上的未定义行为,有时会导致应用程序崩溃。意识到 lock_guard 不支持手动释放,需要迁移到 unique_lock。