C++ 互斥锁

目录

  1. 互斥锁的基本概念
  2. stdmutex-详解
  3. stdlock_guard-详解
  4. stdunique_lock-详解
  5. stdrecursive_mutex-详解
  6. stdtimed_mutex-详解
  7. 死锁预防与最佳实践

1. 互斥锁的基本概念

什么是互斥锁?

互斥锁(Mutual Exclusion,简称 mutex)是一种同步原语,用于保护共享资源,防止多个线程同时访问,从而避免数据竞争和不一致。

为什么需要互斥锁?

#include <iostream>
#include <thread>
#include <vector>

int shared_counter = 0;

void increment_unsafe() {
    for (int i = 0; i < 100000; ++i) {
        ++shared_counter;  // 数据竞争!
    }
}

int main() {
    std::thread t1(increment_unsafe);
    std::thread t2(increment_unsafe);
    
    t1.join();
    t2.join();
    
    std::cout << "Final counter: " << shared_counter << std::endl;
    // 结果可能不是200000,因为存在数据竞争
    return 0;
}

2. std::mutex 详解

核心成员函数

2.1 lock() 函数

作用:获取互斥锁的所有权。如果互斥锁已被其他线程持有,则当前线程阻塞直到锁可用。

函数签名void lock();

参数:无

返回值:void

注意事项

  • 不能递归调用(同一线程重复锁定会导致未定义行为)
  • 必须与unlock()配对使用
  • 可能抛出std::system_error异常
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_counter = 0;

void increment_safe() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();      // 获取锁
        ++shared_counter;
        mtx.unlock();    // 释放锁
    }
}

int main() {
    std::thread t1(increment_safe);
    std::thread t2(increment_safe);
    
    t1.join();
    t2.join();
    
    std::cout << "Final counter: " << shared_counter << std::endl; // 保证是200000
    return 0;
}

易错点

// 错误示例:异常安全問題
void risky_function() {
    mtx.lock();
    if (some_condition) {
        throw std::runtime_error("Error!"); // 如果抛出异常,锁不会被释放!
        // mtx.unlock() 不会被调用,导致死锁
    }
    mtx.unlock();
}
2.2 try_lock() 函数

作用:尝试获取互斥锁,不阻塞线程。立即返回获取结果。

函数签名bool try_lock();

参数:无

返回值

  • true:成功获取锁
  • false:锁已被其他线程持有
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::mutex mtx;

void try_lock_example(int id) {
    for (int i = 0; i < 3; ++i) {
        if (mtx.try_lock()) {
            std::cout << "Thread " << id << " acquired the lock" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            mtx.unlock();
            return;
        } else {
            std::cout << "Thread " << id << " failed to acquire the lock" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }
}

int main() {
    std::thread t1(try_lock_example, 1);
    std::thread t2(try_lock_example, 2);
    
    t1.join();
    t2.join();
    return 0;
}

使用场景

  • 非阻塞的锁获取尝试
  • 避免线程长时间阻塞
  • 实现简单的自旋锁逻辑
2.3 unlock() 函数

作用:释放互斥锁的所有权。

函数签名void unlock();

参数:无

返回值:void

注意事项

  • 必须由锁的持有者调用
  • 调用未锁定的mutex的unlock()是未定义行为
  • 必须与lock()或成功的try_lock()配对使用
// 正确使用示例
void correct_usage() {
    mtx.lock();
    try {
        // 临界区代码
        protected_operation();
        mtx.unlock();  // 确保在所有路径上都释放锁
    } catch (...) {
        mtx.unlock();  // 异常时也要释放锁
        throw;
    }
}

3. std::lock_guard 详解

RAII风格的自动锁管理

作用:在构造时自动加锁,在析构时自动解锁,确保异常安全。

构造函数
explicit lock_guard(mutex_type& m);  // 构造时锁定mutex

使用示例

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

std::mutex mtx;
int shared_counter = 0;

void increment_with_guard() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 构造时自动加锁
        ++shared_counter;
        // lock析构时自动解锁,即使抛出异常也会解锁
    }
}

int main() {
    std::thread t1(increment_with_guard);
    std::thread t2(increment_with_guard);
    
    t1.join();
    t2.join();
    
    std::cout << "Final counter: " << shared_counter << std::endl;
    return 0;
}

优势

  • 异常安全:保证锁总是被释放
  • 代码简洁:不需要手动调用unlock()
  • 作用域明确:锁的生命周期与作用域绑定

注意事项

  • 锁的生命周期受作用域限制
  • 不能手动解锁,只能在析构时解锁
  • 不支持延迟锁定或条件变量

4. std::unique_lock 详解

更灵活的RAII锁管理

特点:比lock_guard更灵活,支持延迟锁定、条件变量、手动解锁等。

核心构造函数
unique_lock() noexcept;                          // 1. 默认构造,不管理任何mutex
explicit unique_lock(mutex_type& m);            // 2. 构造时立即锁定
unique_lock(mutex_type& m, std::defer_lock_t t); // 3. 延迟锁定
unique_lock(mutex_type& m, std::try_to_lock_t t); // 4. 尝试锁定
unique_lock(mutex_type& m, std::adopt_lock_t t); // 5. 接管已锁定的mutex
4.1 基本用法
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void basic_unique_lock() {
    std::unique_lock<std::mutex> lock(mtx);  // 立即锁定
    // 临界区代码
    std::cout << "Thread " << std::this_thread::get_id() << " in critical section" << std::endl;
    // 自动解锁
}
4.2 延迟锁定
void deferred_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 此时mutex未被锁定
    
    // ... 执行一些非临界区操作
    
    lock.lock();  // 手动锁定
    // 临界区代码
    // 自动解锁
}
4.3 尝试锁定
void try_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
    if (lock.owns_lock()) {
        // 成功获取锁
        std::cout << "Lock acquired successfully" << std::endl;
    } else {
        // 获取锁失败,执行替代逻辑
        std::cout << "Failed to acquire lock, doing alternative work" << std::endl;
    }
}
4.4 手动锁定/解锁
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void manual_lock_unlock() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    
    // 手动控制锁定时机
    std::cout << "Doing some non-critical work..." << std::endl;
    
    lock.lock();  // 手动锁定
    std::cout << "In critical section" << std::endl;
    lock.unlock(); // 手动解锁
    
    std::cout << "Doing more non-critical work..." << std::endl;
    
    lock.lock();  // 再次锁定
    std::cout << "Back in critical section" << std::endl;
    // 自动解锁
}
4.5 与条件变量配合使用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;

void producer() {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::unique_lock<std::mutex> lock(mtx);
        data_queue.push(i);
        std::cout << "Produced: " << i << std::endl;
        lock.unlock();
        cv.notify_one();  // 通知消费者
    }
    
    std::unique_lock<std::mutex> lock(mtx);
    finished = true;
    cv.notify_one();
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 等待条件满足,wait会自动释放锁并在唤醒时重新获取
        cv.wait(lock, []{ return !data_queue.empty() || finished; });
        
        if (finished && data_queue.empty()) break;
        
        while (!data_queue.empty()) {
            int data = data_queue.front();
            data_queue.pop();
            std::cout << "Consumed: " << data << std::endl;
        }
    }
}
核心成员函数详解
函数 作用 返回值 注意事项
lock() 锁定关联的mutex void 如果已锁定会抛出异常
try_lock() 尝试锁定 bool 非阻塞,立即返回结果
try_lock_for(duration) 在指定时间内尝试锁定 bool 仅适用于timed_mutex
try_lock_until(timepoint) 在指定时间点前尝试锁定 bool 仅适用于timed_mutex
unlock() 解锁关联的mutex void 必须已持有锁
release() 释放mutex所有权但不解锁 mutex_type* 需要手动管理解锁
owns_lock() 检查是否持有锁 bool 判断锁状态
operator bool() 检查是否持有锁 bool 同owns_lock()

5. std::recursive_mutex 详解

可重入互斥锁

作用:允许同一线程多次锁定同一个互斥锁。

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

std::recursive_mutex rec_mtx;

void recursive_function(int depth) {
    if (depth <= 0) return;
    
    std::lock_guard<std::recursive_mutex> lock(rec_mtx);
    std::cout << "Depth: " << depth << std::endl;
    recursive_function(depth - 1);  // 递归调用,需要可重入锁
    // 如果是std::mutex,这里会导致死锁
}

int main() {
    std::thread t(recursive_function, 3);
    t.join();
    return 0;
}

使用场景

  • 递归函数中的临界区保护
  • 可能被同一线程多次调用的函数
  • 需要嵌套锁定的复杂逻辑

注意事项

  • 性能略低于std::mutex
  • 锁定次数必须与解锁次数匹配
  • 避免过度使用,可能表示设计问题

6. std::timed_mutex 详解

带超时功能的互斥锁

6.1 try_lock_for()

作用:在指定时间内尝试获取锁

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

std::timed_mutex timed_mtx;

void timed_lock_example(int id) {
    auto timeout = std::chrono::milliseconds(100);
    
    if (timed_mtx.try_lock_for(timeout)) {
        std::cout << "Thread " << id << " acquired the lock" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        timed_mtx.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire lock within timeout" << std::endl;
    }
}

int main() {
    std::thread t1(timed_lock_example, 1);
    std::thread t2(timed_lock_example, 2);
    
    t1.join();
    t2.join();
    return 0;
}
6.2 try_lock_until()

作用:在指定时间点前尝试获取锁

void try_until_example() {
    auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
    
    if (timed_mtx.try_lock_until(deadline)) {
        std::cout << "Lock acquired before deadline" << std::endl;
        // ... 临界区代码
        timed_mtx.unlock();
    } else {
        std::cout << "Failed to acquire lock before deadline" << std::endl;
    }
}

7. 死锁预防与最佳实践

7.1 使用std::lock同时锁定多个互斥锁

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

std::mutex mtx1, mtx2;

void safe_lock_order(int id) {
    // 使用std::lock同时锁定多个mutex,避免死锁
    std::lock(mtx1, mtx2);
    
    // 使用std::adopt_lock接管已锁定的mutex
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    
    std::cout << "Thread " << id << " acquired both locks safely" << std::endl;
}

void unsafe_lock_order(int id) {
    // 错误的顺序可能导致死锁
    mtx1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(1));  // 增加死锁概率
    mtx2.lock();  // 可能在此处死锁
    
    std::cout << "Thread " << id << " acquired both locks" << std::endl;
    
    mtx2.unlock();
    mtx1.unlock();
}

7.2 最佳实践总结

  1. 优先使用RAII包装器

    // 好:异常安全
    {
        std::lock_guard<std::mutex> lock(mtx);
        // 临界区代码
    }
    
    // 不好:手动管理容易出错
    mtx.lock();
    // 如果这里抛出异常,锁不会被释放!
    mtx.unlock();
    
  2. 保持锁的粒度尽可能小

    // 好:锁粒度小
    void good_design() {
        // 非临界区操作
        do_non_critical_work();
        
        {
            std::lock_guard<std::mutex> lock(mtx);
            do_critical_work();
        }
        
        // 更多非临界区操作
        do_more_work();
    }
    
    // 不好:锁粒度太大
    void bad_design() {
        std::lock_guard<std::mutex> lock(mtx);  // 锁定时间过长
        do_non_critical_work();
        do_critical_work();
        do_more_work();
    }
    
  3. 避免嵌套锁定的死锁

    // 使用std::lock避免死锁
    void no_deadlock() {
        std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock);
        std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock);
        std::lock(lock1, lock2);  // 同时锁定,避免死锁
    }
    
  4. 考虑使用std::scoped_lock(C++17)

    // C++17推荐方式
    void scoped_lock_example() {
        std::scoped_lock lock(mtx1, mtx2);  // 自动处理多个mutex
        // 临界区代码
    }
    

完整示例:线程安全的队列

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <chrono>

template<typename T>
class ThreadSafeQueue {
private:
    mutable std::mutex mtx;
    std::queue<T> data_queue;
    std::condition_variable data_cond;
    
public:
    ThreadSafeQueue() = default;
    
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx);
        data_queue.push(std::move(value));
        data_cond.notify_one();
    }
    
    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lock(mtx);
        if (data_queue.empty()) {
            return false;
        }
        value = std::move(data_queue.front());
        data_queue.pop();
        return true;
    }
    
    void wait_and_pop(T& value) {
        std::unique_lock<std::mutex> lock(mtx);
        data_cond.wait(lock, [this]{ return !data_queue.empty(); });
        value = std::move(data_queue.front());
        data_queue.pop();
    }
    
    bool empty() const {
        std::lock_guard<std::mutex> lock(mtx);
        return data_queue.empty();
    }
    
    size_t size() const {
        std::lock_guard<std::mutex> lock(mtx);
        return data_queue.size();
    }
};

int main() {
    ThreadSafeQueue<int> ts_queue;
    
    // 生产者线程
    std::thread producer( {
        for (int i = 0; i < 10; ++i) {
            ts_queue.push(i);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    });
    
    // 消费者线程
    std::thread consumer( {
        for (int i = 0; i < 10; ++i) {
            int value;
            ts_queue.wait_and_pop(value);
            std::cout << "Consumed: " << value << std::endl;
        }
    });
    
    producer.join();
    consumer.join();
    
    return 0;
}

这份详细的C++互斥锁教程涵盖了从基础概念到高级用法的所有核心内容,包括各种锁类型的使用方法、注意事项和最佳实践。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐