Bootstrap

C++学习记录:std::lock_guard<std::mutex> 用法解析

一、概念

在多线程编程中,资源竞争和数据竞争是常见的问题。为了避免多个线程同时访问共享资源,我们需要使用同步机制。而 std::mutex 是 C++ 提供的基础同步原语之一,用来保证在同一时刻只有一个线程可以访问某个共享资源。为了简化对 std::mutex 的使用,C++ 标准库提供了 std::lock_guard,一个自动管理锁的类。

std::lock_guard 的构造函数在对象创建时加锁,而析构函数则在对象销毁时自动解锁。这样,我们可以避免忘记解锁或者在异常发生时未能释放锁的风险,从而减少了死锁和资源泄漏的可能。

  • 互斥量 (std::mutex):一种用来控制多个线程访问共享资源的同步机制。只有获得互斥锁的线程才能访问被保护的资源。
  • std::lock_guard:一个包装类,自动管理互斥量的锁定和解锁,提供异常安全保障。

二、工作原理

在 C++ 中,std::lock_guard 的构造函数会立即锁定一个传入的 std::mutex 对象,并在对象的生命周期结束时释放锁。这个机制使得加锁和解锁的过程自动化,避免了手动加锁、解锁时可能引发的错误。

示例:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex g_mutex;  // 全局互斥量

void thread_function(int id) {
    // 在此作用域内加锁
    std::lock_guard<std::mutex> lock(g_mutex);  // 自动加锁
    std::cout << "Thread " << id << " is executing.\n";
    // lock 离开作用域后自动解锁
}

int main() {
    std::thread t1(thread_function, 1);
    std::thread t2(thread_function, 2);

    t1.join();
    t2.join();
    
    return 0;
}

 三、优势

自动加锁与解锁
  • 在使用 std::lock_guard 时,无需显式地调用 mutex.lock()mutex.unlock()。当 std::lock_guard 对象被创建时,它会自动获取锁,而当它超出作用域时,析构函数会自动释放锁。
  • 这种自动化管理方式降低了由于人为错误或遗漏导致的问题,例如忘记解锁或多次解锁。
异常安全
  • 如果在加锁后发生异常,std::lock_guard 会确保在异常发生时,锁会被正确释放,防止死锁或资源泄漏。
  • 这是因为 std::lock_guard 的析构函数会在作用域结束时自动执行解锁操作。
 简洁性与可维护性
  • 使用 std::lock_guard 可以简化代码,减少对锁的显式管理。开发人员只需关注资源的保护逻辑,而不需要担心锁的管理细节。

 四、错误示例:手动管理锁

虽然 std::lock_guard 提供了自动化的加锁和解锁机制,但如果手动管理锁,容易出现一些问题,如死锁或忘记解锁。

std::mutex mtx;
void thread_function() {
    mtx.lock();  // 手动加锁
    // 一些代码可能抛出异常
    mtx.unlock();  // 手动解锁
}

如果在 mtx.unlock() 之前抛出异常,锁将永远不会被释放,可能导致死锁。因此,std::lock_guard 的使用提供了更为安全和简洁的解决方案。 

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;