C++ 互斥锁的使用

news/2025/2/23 19:51:45

mutex

std::mutex 是C++标准库中用于线程同步的互斥锁机制,主要用于保护共享资源,避免多个线程同时访问导致的竞态条件。

它提供了以下功能:

  • 加锁(lock:阻塞当前线程,直到获取锁。

  • 解锁(unlock:释放锁,允许其他线程获取锁。

  • 尝试加锁(try_lock:尝试获取锁,如果锁已被占用则立即返回。

使用全局锁案例

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx; // 定义一个全局互斥锁
int shared_data = 0;

void increment() {
    for (int i = 0; i < 10000; i++) {
        mtx.lock(); // 加锁
        shared_data++;
        mtx.unlock(); // 解锁
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();

    cout << "共享数据: " << shared_data << endl; // 输出应该是20000
    return 0;
}

使用传参锁案例

如果线程对象传参给可调⽤对象时,使⽤引⽤⽅式传参,实参位置需要加上ref(obj)的⽅式,主要原因是thread本质还是系统库提供的线程API的封装,thread 构造取到参数包以后,要调⽤创建线程的API,还是需要将参数包打包成⼀个结构体传参过去,那么打包成结构体时,参考包对象就会拷⻉给结构体对象,使⽤ref传参的参数,会让结构体中的对应参数成员类型推导为引⽤,这样才能实现引⽤传参。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int shared_data = 0;

void increment(mutex& mtx) {
    for (int i = 0; i < 10000; i++) {
        mtx.lock(); // 加锁
        shared_data++;
        mtx.unlock(); // 解锁
    }
}

int main() {
    mutex mtx; // 使用传参互斥锁
    thread t1(increment,ref(mtx));//需要使用 ref 包装锁,不然会报错
    thread t2(increment,ref(mtx));
    t1.join();
    t2.join();

    cout << "共享数据: " << shared_data << endl; // 输出应该是20000
    return 0;
}

timed_mutex

std::timed_mutex 是C++11引入的一种互斥锁类型,用于多线程编程中控制对共享资源的并发访问。它提供了超时机制,允许线程在尝试获取锁时设置一个时间限制,从而避免无限等待锁释放,降低死锁风险。

timed_mutex 跟 mutex完全类似,只是额外提供 try_lock_for 和 try_lock_untile 的接⼝,这两个接⼝跟 try_lock 类似,只是他不会⻢上返回,⽽是直接进⼊阻塞,直到时间条件到了或者解锁了就会唤醒试图获取锁资源。
  • try_lock_for(duration):尝试在指定的时间内获取锁,如果超时则返回 false

  • try_lock_until(time_point):尝试在指定的时间点之前获取锁,如果超时则返回 false。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
timed_mutex mtx;

void func(int id) {
    chrono::seconds timeout(3); // 设置超时时间为3秒
    if (mtx.try_lock_for(timeout)) {
        cout << "线程 " << id << " 成功获取锁,执行临界区代码..." << endl;
        this_thread::sleep_for(chrono::seconds(5)); // 模拟工作负载
        mtx.unlock();
    }
    else {
        cout << "线程 " << id << " 超时未能获取锁,继续执行其他任务..." << endl;
    }
}

int main() {
    thread t1(func, 1);
    thread t2(func, 2);
    t1.join();
    t2.join();

    return 0;
}

recursive_mutex

recursive_mutex 跟 mutex 完全类似,允许同一个线程多次获取锁而不会导致死锁,这种互斥锁特别适用于递归函数或嵌套调用的场景。 recursive_mutex 提供排他性递归所有权语义:
  1. 调⽤⽅线程在从它成功调⽤ lock 或 try_lock 开始的时期⾥占有 recursive_mutex。此时期之内,线程可以进⾏对 lock 或 try_lock 的附加调⽤。所有权的时期在线程进⾏匹配次数的 unlock 调⽤时结束。
  2. 线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调⽤ lock)或收到 false 返回值(对于调⽤ try_lock)。
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
recursive_mutex rec_mutex;

void recursiveFunction(int count) {
    if (count <= 0) return;
    rec_mutex.lock();
    cout << "Lock acquired. Count: " << count << endl;
    // 递归调用
    recursiveFunction(count - 1);

    rec_mutex.unlock();
    cout << "Lock released. Count: " << count << endl;
}

int main() {
    thread t(recursiveFunction, 3);
    t.join();
    return 0;
}

lock_guard

std::lock_guard 是 C++ 标准库中用于管理互斥锁的 RAII(Resource Acquisition Is Initialization,资源获取即初始化)工具。它通过构造函数自动获取互斥锁,并在析构函数中自动释放锁,从而确保互斥锁的正确管理,避免因忘记解锁而导致的死锁问题。

主要特性

  1. 自动管理锁:通过构造函数获取锁,析构函数释放锁,无需手动调用 lock()unlock()

  2. 防止死锁:确保即使在异常情况下也能正确释放锁。

  3. 不可移动和复制:std::lock_guard 不支持拷贝或移动构造,确保锁的唯一性。

构造函数

explicit lock_guard(MutexType& mutex, std::adopt_lock_t tag = std::defer_lock);
  • MutexType& mutex:要管理的互斥锁对象,如mutex,timed_mutex,recersive_mutex。

  • std::adopt_lock_t tag:可选参数,表示当前线程已经持有锁,默认值是 std::defer_lock,表示 lock_guard 会在构造时自动尝试加锁。如果传入 std::adopt_lock,则表示当前线程已经通过其他方式(例如 std::lock 或手动调用 lock())持有锁,lock_guard 不会再次尝试加锁,而是直接接管锁的管理。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
void print_block(int n, char c) {
    lock_guard<mutex> lock(mtx);  // 默认加锁,无需手动解锁
    for (int i = 0; i < n; ++i) {
        cout << c;
    }
    cout << '\n';
}

int main() {
    thread t1(print_block, 50, '*');
    thread t2(print_block, 50, '$');
    t1.join();
    t2.join();
    return 0;
}

unique_lock

unique_lock也是C++11提供的⽀持RAII⽅式管理互斥锁资源的类,相⽐ lock_guard 他的功能⽀持
更丰富复杂。

主要特性

  1. 延迟加锁:可以在构造时不立即加锁,而是通过显式调用 lock()unlock() 来控制锁的获取和释放。

  2. 支持条件变量:可以与 std::condition_variable 配合使用,支持线程间的同步。

  3. 可选锁管理:可以选择是否持有锁,或者在构造时直接接管已持有的锁。

  4. 支持多种互斥锁类型:可以与 std::mutexstd::recursive_mutexstd::timed_mutexstd::shared_mutex 配合使用。

构造函数

template <typename Mutex>
unique_lock() noexcept;  // 默认构造,不绑定任何锁

template <typename Mutex>
explicit unique_lock(Mutex& m, std::defer_lock_t);  // 延迟加锁

template <typename Mutex>
unique_lock(Mutex& m, std::try_to_lock_t);  // 尝试加锁,失败时不阻塞

template <typename Mutex>
unique_lock(Mutex& m, std::adopt_lock_t);  // 假设已持有锁,不加锁

template <typename Mutex>
unique_lock(Mutex& m);  // 默认加锁

template <typename Mutex>
unique_lock(Mutex& m, std::unique_lock<Mutex>&& other);  // 移动构造

主要成员函数

  1. lock():尝试加锁,如果锁已被占用则阻塞。

  2. try_lock():尝试加锁,如果锁已被占用则立即返回 false

  3. unlock():释放锁。

  4. owns_lock():检查是否持有锁。

  5. mutex():获取绑定的互斥锁对象。

  6. release():释放锁的所有权,但不释放锁本身。

  7. swap():交换两个 std::unique_lock 的状态。

lock和try_lock

lock是⼀个函数模板,可以⽀持对多个锁对象同时锁定,如果其中⼀个锁对象没有锁住,lock函数
会把已经锁定的对象解锁⽽进⼊阻塞,直到锁定所有的所有的对象,它的主要目的是避免死锁问题,通过一次性尝试锁定多个锁,确保所有锁都被成功锁定后才返回。
try_lock也是⼀个函数模板,尝试对多个锁对象进⾏同时尝试锁定,如果全部锁对象都锁定了,返 回-1,如果某⼀个锁对象尝试锁定失败,把已经锁定成功的锁对象解锁,并则返回这个对象的下标
(第⼀个参数对象,下标从0开始算)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx1;
mutex mtx2;
void worker() {
    lock(mtx1, mtx2);  // 同时锁定两个锁
    unique_lock<mutex> lock1(mtx1, std::adopt_lock);  // 需要创建 unique_lock 自动释放锁
    unique_lock<mutex> lock2(mtx2, std::adopt_lock);

    cout << "线程进入临界区..." << endl;
    this_thread::sleep_for(chrono::seconds(1));  // 模拟工作负载
    cout << "线程退出临界区..." << endl;
}

int main() {
    thread t1(worker);
    thread t2(worker);
    t1.join();
    t2.join();
    return 0;
}

call_once

std::call_once 是 C++11 引入的一个函数模板,用于确保某个操作(如初始化或配置加载)在多线程环境中只被调用一次。它结合了 std::once_flag,能够高效地实现线程安全的单次执行。

基本用法

std::call_once 的函数原型如下:

template <class Callable, class... Args>
void call_once(std::once_flag& flag, Callable&& f, Args&&... args);
  • std::once_flag:一个标记对象,用于记录操作是否已经执行过。

  • Callable&& f:需要执行的可调用对象(如函数、lambda 表达式、函数对象等)。

  • Args&&... args:传递给可调用对象的参数。

工作原理

  1. 首次调用:如果 std::once_flag 标记的操作尚未执行,则 std::call_once 会调用 f,并设置 flag 标记为“已执行”。

  2. 后续调用:如果 flag 已标记为“已执行”,则后续调用 std::call_once 的线程会直接跳过 f 的执行。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::once_flag flag;
void init(int id) {
    cout << "Initialization function called once." << endl;
    cout << "thread " << id << " is running." << endl;
}

void worker(int id) {
    call_once(flag, init,id);  // 确保 init 只被调用一次
}

int main() {
    thread t1(worker, 1);
    thread t2(worker, 2);
    thread t3(worker, 3);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}


http://www.niftyadmin.cn/n/5863722.html

相关文章

微信小程序-组件复用机制behaviors

简介: 小程序的 behaviors方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。 使用 behaviors 复用代码 如果需要复用代码&#xff0c;可以通过 Behavior() 方法定义一个行为&#xff0c;每个行为可…

vue2.x 中子组件向父组件传递数据主要通过 $emit 方法触发自定义事件方式实现

在 Vue 2.x 中&#xff0c;子组件向父组件传递数据主要通过 自定义事件 的方式实现。具体步骤如下&#xff1a; 1. 子组件通过 $emit 触发事件 子组件可以使用 $emit 方法触发一个自定义事件&#xff0c;并将数据作为参数传递给父组件。 语法&#xff1a; this.$emit(事件名…

0基础学前端-----CSS DAY13

HTML和CSS3提高 视频参考&#xff1a;B站Pink老师 本节重点&#xff1a;HTML和CSS3的新增特性 本章目录 HTML和CSS3提高1.HTML5新特性1.1HTML新增的语义化标签&#xff08;div无语义&#xff09;1.2 HTML5新增的多媒体标签1.2.1 视频<video>1.2.2 音频audio1.2.3 多媒体…

蓝桥杯——按键

一&#xff1a;按键得原理图 二&#xff1a;按键的代码配置 step1 按键原理图对应引脚配置为输入状态 step2 在GPIO中将对应引脚设置为上拉模式 step3 在fun.c中写按键扫描函数 写完后的扫描函数需放在主函数中不断扫描 扫描函数主要通过两个定义变量的值来判断&#xf…

车载诊断架构 --- LIN节点路由转发注意事项

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

先进制造aps专题三十 用免费生产排程软件isuperaps进行长期生产计划制定

isuperaps是生产排产软件&#xff0c;同时也可以用来制定长期生产计划 通过isuperaps制定长期生产计划&#xff0c;一个指导原则就是大bom, 单工序&#xff0c;大bom的意思是bom中只包含主要的半成品和原料&#xff0c;单工序的意思是半成品/产品生产以工厂或车间为基本生产单…

【ACM独立出版丨EI检索稳定丨高录用丨Fellow出席演讲】2025年生成式人工智能与数字媒体国际学术会议(GADM 2025)

随着人工智能技术的快速发展&#xff0c;生成式AI在艺术创作、内容生成、用户体验等方面展现出巨大的潜力和挑战。2025年生成式人工智能与数字媒体国际学术会议(GADM 2025&#xff09;将于2025年3月14-17日召开。本次会议将汇聚来自全球的学术专家、研究者和行业领袖&#xff0…

毕业项目推荐:基于yolov8/yolo11的100种中药材检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…