C++线程 锁

关于C++中线程和锁的介绍

一、线程类型(C++标准库)

C++标准库提供基础线程类型,用于创建和管理并发执行单元:

1. `std::thread(C++11)

  • 核心功能:创建独立线程,执行可调用对象(函数、lambda、函数对象等)

  • 关键特性

    • 不可复制,仅可移动(线程所有权唯一)
    • 必须显式调用join()(等待线程结束)或detach()(分离线程),否则析构时触发std::terminate()
    • 无内置取消机制,需手动实现线程退出逻辑
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <thread>
    void task(int param) { /* 线程任务 */ }

    int main() {
    std::thread t1(task, 42); // 传递参数
    std::thread t2([]{ /* lambda任务 */ });

    t1.join(); // 等待t1完成
    t2.detach(); // t2后台运行,脱离主线程控制
    return 0;
    }

2. std::jthread(C++20)

  • 核心功能std::thread的增强版,自带RAII特性

  • 关键特性

    • 析构时自动调用join(),无需手动管理,避免资源泄漏
    • 支持通过std::stop_tokenstd::stop_source实现线程取消
    • 兼容std::thread的所有操作,可无缝替换
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <thread>
    void cancelable_task(std::stop_token st) {
    while (!st.stop_requested()) { // 检查取消请求
    /* 循环任务 */
    }
    }

    int main() {
    std::jthread t(cancelable_task); // 自动join
    // ... 其他操作 ...
    t.request_stop(); // 请求线程停止
    return 0; // t析构时自动join
    }

说明:C++标准中无shared_threadtimed_thread,线程的共享性通过同步机制实现,超时控制通过条件变量或带超时的锁操作实现。

二、互斥锁与RAII封装

互斥锁用于保护共享资源,RAII(资源获取即初始化)封装确保锁的安全管理:

1. 基础互斥锁类型

类型 特性 适用场景
std::mutex 独占所有权,非递归 基本互斥场景
std::recursive_mutex 允许同一线程多次锁定,需对应次数解锁 递归调用场景(谨慎使用,易隐藏设计问题)
std::shared_mutex(C++17) 支持共享锁(读)和独占锁(写) 读多写少场景(如缓存、配置读取)
std::timed_mutex 支持超时锁定 需要超时控制的独占场景
std::recursive_timed_mutex 递归+超时锁定 递归调用且需超时控制

2. RAII锁封装类(核心工具)

通过RAII机制自动管理锁的生命周期,避免手动lock()/unlock()导致的死锁。

(1)std::lock_guard(C++11)
  • 特性:构造时锁定,析构时解锁,行为不可变

  • 优势:轻量、高效,无额外开销

  • 限制:不可手动解锁、不可转移所有权、不支持延迟锁定

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    #include <mutex>
    std::mutex mtx;
    int shared_value = 0;

    void safe_increment() {
    std::lock_guard<std::mutex> lock(mtx); // 自动锁定
    shared_value++; // 临界区操作
    } // 自动解锁,即使发生异常
(2)std::unique_lock(C++11)
  • 特性:灵活控制锁状态,支持手动锁定/解锁、延迟锁定、超时锁定

  • 优势:功能全面,可配合条件变量使用

  • 开销:略高于lock_guard(因需维护锁状态)

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void flexible_operation() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟锁定

    // 预处理(无需锁)
    prepare_operation();

    lock.lock(); // 手动锁定
    perform_operation(); // 临界区
    lock.unlock(); // 提前解锁(可选)

    // 后续操作(无需锁)
    cleanup_operation();
    }
(3)std::shared_lock(C++14)
  • 特性:配合std::shared_mutex实现共享锁(读锁)

  • 优势:多个线程可同时持有共享锁,提高读操作并发度

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <shared_mutex>
    std::shared_mutex smtx;
    std::string config_data;

    // 读操作(共享访问)
    std::string read_config() {
    std::shared_lock<std::shared_mutex> lock(smtx); // 共享锁
    return config_data; // 多个读线程可同时执行
    }

    // 写操作(独占访问)
    void update_config(const std::string& new_data) {
    std::unique_lock<std::shared_mutex> lock(smtx); // 独占锁
    config_data = new_data; // 写操作时阻塞所有读写
    }
(4)std::scoped_lock(C++17)
  • 特性:同时锁定多个互斥量,内部使用死锁避免算法

  • 优势:解决多锁场景下的死锁问题(如按固定顺序锁定)

  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    std::mutex mtx1, mtx2;
    int a = 0, b = 0;

    void swap_values() {
    // 同时锁定多个互斥量,避免死锁
    std::scoped_lock lock(mtx1, mtx2); // 顺序不影响,内部自动处理
    std::swap(a, b);
    } // 自动解锁所有互斥量

三、锁操作标签(Tag Types)

通过标签控制锁的行为,实现特殊场景下的同步逻辑:

标签 作用 适用类 典型场景
std::adopt_lock 接管已获取的锁,不重复锁定 lock_guard/unique_lock 手动锁定后交给RAII管理
std::try_to_lock 尝试锁定,不阻塞,成功则持有 unique_lock 非阻塞获取锁,避免线程挂起
std::defer_lock 延迟锁定,构造时不获取锁 unique_lock 灵活控制锁定时机(如条件判断后)

1. std::adopt_lock 示例

1
2
3
4
5
6
void manual_lock_management() {
mtx.lock(); // 手动锁定(必须成功)
// 接管锁的释放权,避免手动unlock()遗漏
std::lock_guard<std::mutex> lock(mtx, std::adopt_lock);
// 临界区操作
} // 自动解锁

2. std::try_to_lock 示例

1
2
3
4
5
6
7
8
9
void non_blocking_operation() {
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);

if (lock.owns_lock()) { // 检查是否成功获取锁
critical_operation();
} else {
fallback_operation(); // 未获取锁时的备选逻辑
}
}

四、高级操作:超时与所有权转移

1. 超时锁定(unique_lock专属)

避免线程因无法获取锁而永久阻塞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <chrono>
using namespace std::chrono_literals;

void timed_operation() {
std::unique_lock<std::timed_mutex> lock(timed_mtx);

// 尝试锁定,最多等待500ms
if (lock.try_lock_for(500ms)) {
success_operation();
} else {
timeout_handler(); // 超时处理
}

// 绝对时间版本(到某个时间点为止)
auto deadline = std::chrono::system_clock::now() + 1s;
if (lock.try_lock_until(deadline)) {
// 成功锁定
}
}

2. 锁的所有权转移

unique_lock支持移动语义,可在函数间传递锁的所有权:

1
2
3
4
5
6
7
8
9
10
// 函数返回已锁定的锁
std::unique_lock<std::mutex> acquire_lock() {
std::unique_lock<std::mutex> lock(mtx);
return lock; // 移动语义传递所有权
}

void use_locked_resource() {
auto lock = acquire_lock(); // 接收锁(仍处于锁定状态)
access_protected_resource(); // 直接使用临界资源
} // 锁在此处析构,自动解锁

五、最佳实践与避坑指南

  1. 优先使用RAII封装:始终用lock_guard/unique_lock等管理锁,避免手动lock()/unlock()(易因异常导致死锁)。

  2. 最小化临界区:锁的范围应尽可能小,仅包裹必要的共享资源操作,减少线程阻塞时间。

  3. 死锁预防

    • 多锁场景用scoped_lock或按固定顺序加锁(如按地址排序)。
    • 避免在持有锁时调用外部函数(可能隐式获取其他锁)。
    • 结合try_to_lock和超时机制,设置锁获取的最大等待时间。
  4. 锁类型选择策略

    • 简单场景→lock_guard(性能最优)。
    • 需灵活控制(如条件变量、超时)→unique_lock
    • 读多写少→shared_mutex+shared_lock/unique_lock
    • 多锁同时锁定→scoped_lock
  5. 线程管理:优先使用jthread(C++20+),避免detach()(难以控制生命周期)。

这些组件共同构成了C++多线程同步的完整体系,理解其特性与适用场景是编写安全、高效并发程序的基础。实际开发中,还需结合条件变量(std::condition_variable)、原子操作(std::atomic)等机制解决复杂同步问题。