当前位置: 首页 > article >正文

C++中的 互斥量

1.概念:

为什么:线程的异步性,不是按照时间来的!!!

C++并发以及多线程的秘密-CSDN博客

目的

多线程编程中,当多个线程可能同时访问和修改共享资源时,会导致数据不一致或程序错误。互斥量提供了一种加锁机制,线程在访问共享资源前必须先获取互斥量的锁,访问结束后释放锁,这样其他线程才能获取锁并访问共享资源。

原理:

互斥量内部维护一个状态,表示它是否被锁定。当一个线程成功获取锁时,互斥量被锁定,其他线程试图获取锁时会被阻塞,直到锁被释放

2.基本用法

C++ 标准库在 <mutex> 头文件中提供了几种类型的互斥量, std::mutex std::timed_mutex

lock函数和unlock函数

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

int sharedResource = 0;
std::mutex mtx;
void increment() {
    for (int i = 0; i < 10; ++i) {
        mtx.lock();
        ++sharedResource;
       cout<<++sharedResource<<endl
        mtx.unlock();
		
    }
}

void decrement() {
    for (int i = 0; i < 10; ++i) {
        mtx.lock();
        --sharedResource;
        cout<<sharedResource<<endl;
		mtx.unlock();
		
    }
}
int main()
   {
	 std::thread thread1(increment);
    std::thread thread2(decrement);

    thread1.join();
    thread2.join();

    std::cout << "Final value of sharedResource: " << sharedResource << std::endl;

    return 0;

 
}

结果如下:

1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0

结果展现清晰知道会有独占思想

2.lock_guard函数

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

std::mutex mtx;
int n=0;

void func1() {
    lock_guard<mutex> lock(mtx);
     n++;
}
void func2() {
    lock_guard<mutex> lock(mtx);
    n--;
}

int main() {
    thread t1(func1);
    cout << n << endl;
    thread t2(func2);
    cout << n << endl;
    t1.join();
    t2.join();
}

3.死锁以及解决办法

概念:

死锁是指在多线程或多进程环境中,两个或多个执行单元(线程、进程等)因竞争系统资源或彼此通信而造成的一种阻塞现象,若无外力作用,这些执行单元都将无法推进。

死锁产生的必要条件

  1. 互斥条件:资源在同一时间只能被一个执行单元使用。例如,打印机在打印一份文档时,不能同时为另一份文档服务。

  2. 占有并等待条件:一个执行单元持有至少一个资源,并在等待获取其他执行单元持有的额外资源。比如,线程 A 持有资源 R1,同时等待资源 R2,而资源 R2 被线程 B 持有。

  3. 不可剥夺条件:资源不能被强制从占有者手中夺走,只能由占有者主动释放。例如,一个线程获得了一个文件的独占访问权,其他线程不能强行剥夺这个权限。

  4. 循环等待条件:存在一个执行单元的循环链,链中的每个执行单元都在等待下一个执行单元持有的资源。例如,线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,而线程 C 又等待线程 A 持有的资源。

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

std::mutex mutex1;
std::mutex mutex2;

void threadFunction1() {
    mutex1.lock();
    std::cout << "Thread 1 has locked mutex1" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    mutex2.lock();
    std::cout << "Thread 1 has locked mutex2" << std::endl;
    mutex2.unlock();
    mutex1.unlock();
}

void threadFunction2() {
    mutex2.lock();
    std::cout << "Thread 2 has locked mutex2" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    mutex1.lock();
    std::cout << "Thread 2 has locked mutex1" << std::endl;
    mutex1.unlock();
    mutex2.unlock();
}

int main() {
    std::thread thread1(threadFunction1);
    std::thread thread2(threadFunction2);

    thread1.join();
    thread2.join();

    return 0;
}
结果//Thread 1 has locked mutex1Thread 2 has locked mutex2

解析:

在上述代码中,threadFunction1 先锁定 mutex1,然后等待 mutex2,而 threadFunction2 先锁定 mutex2,然后等待 mutex1。如果两个线程同时运行,就会满足死锁的四个条件,导致死锁发生。线程 1 持有 mutex1 并等待 mutex2,线程 2 持有 mutex2 并等待 mutex1,形成循环等待,且资源不能被剥夺,从而造成死锁。

如何解决?

利用另外一个lock_guard

std::lock_guard 是基于 RAII(资源获取即初始化)机制的互斥量管理类。它在构造时自动锁定互斥量,在析构时自动解锁,从而简化了代码并避免手动解锁失败的风险

代表adopt_lock 以及手动锁了(就是用lock)

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

std::mutex mutex1;
std::mutex mutex2;
;

void threadFunction1() {

    lock(mutex1,mutex2);
    lock_guard<mutex>lock1(mutex1,adopt_lock);
    lock_guard<mutex>lock2(mutex2,adopt_lock);
    std::cout << "Thread 1 has locked mutex1 and mutex2" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread 1 is working with both mutexes" << std::endl;
}



  
    void threadFunction2() {

        lock(mutex1,mutex2);
        lock_guard<mutex>lock1(mutex2,adopt_lock);
        lock_guard<mutex>lock2(mutex1,adopt_lock);
        std::cout << "Thread 1 has locked mutex1 and mutex2" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "Thread 1 is working with both mutexes" << std::endl;
    }



int main() {
    std::thread thread1(threadFunction1);
    std::thread thread2(threadFunction2);

    thread1.join();
    thread2.join();

    return 0;
}
Thread 1 has locked mutex1 and mutex2
Thread 1 is working with both mutexes
Thread 1 has locked mutex1 and mutex2
Thread 1 is working with both mutexes

4.unique_lock

std::lock_guard是一个简单的 RAII(Resource Acquisition Is Initialization)类,用于在其生命周期内自动锁定和解锁互斥量。它的优点是简单易用,开销小。然而,std::unique_lock提供了更多的功能和灵活性,在很多场景下可以取代std::lock_guard:

  • 延迟锁定std::unique_lock可以在构造时不立即锁定互斥量,而std::lock_guard在构造时总是立即锁定互斥量。

  • 锁定所有权的转移std::unique_lock允许通过移动语义转移锁定的所有权,这在函数间传递锁定状态时非常有用。

  • 成员函数操作std::unique_lock提供了如lock()try_lock()try_lock_for()try_lock_until()unlock()等成员函数,允许对锁定状态进行更细粒度的控制,而std::lock_guard只在构造和析构时自动锁定和解锁,没有提供这些操作。

4.1三种构造方法

1.std::adopt_lock_t:与std::lock_guard类似,当使用std::adopt_lock作为第二个参数时,表明调用者已经手动锁定了互斥量,std::unique_lock在构造时不会再次锁定,而是在析构时负责解锁。例如:

std::mutex mtx;
mtx.lock();
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);

2.std::defer_lock_t:使用std::defer_lock作为第二个参数时,std::unique_lock构造时不会锁定互斥量,而是处于未锁定状态。之后可以通过调用lock()try_lock()等成员函数来锁定互斥量。例如

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
if (lock.try_lock()) {
    // 成功锁定,执行临界区代码
    lock.unlock();
}

3.std::try_to_lock_t:使用std::try_to_lock作为第二个参数时,std::unique_lock构造时会尝试锁定互斥量,但不会阻塞等待。如果成功锁定,unique_lock对象拥有锁定;否则,处于未锁定状态。例如

std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
    // 成功锁定,执行临界区代码
    lock.unlock();
}

4.2成员函数

  • lock():锁定关联的互斥量。如果互斥量已被锁定,调用线程将阻塞直到互斥量可用。

  • try_lock():尝试锁定关联的互斥量,不会阻塞。如果成功锁定,返回true;否则,返回false

  • try_lock_for(duration):尝试在指定的时间段内锁定关联的互斥量。如果在指定时间内成功锁定,返回(一般参数为chrono::seconds())同理

  • try_lock_until(time_point) 只能time_mutex用

  • unlock():解锁关联的互斥量。

  • owns_lock():返回unique_lock对象是否拥有互斥量的锁定。

  • release():释放unique_lock对象对互斥量的所有权,返回指向关联互斥量的指针,并且unique_lock对象变为不拥有任何互斥量

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

// 使用 std::timed_mutex 来支持带超时的锁定操作
std::timed_mutex mtx;

// 线程函数
void worker(int id) {
    // 使用 unique_lock 管理互斥量
    std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock);

    // 尝试锁定互斥量,不会阻塞
    if (lock.try_lock()) {
        std::cout << "Thread " << id << " acquired the lock using try_lock()." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lock.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire the lock using try_lock()." << std::endl;
    }

    // 尝试在指定时间段内锁定互斥量
    if (lock.try_lock_for(std::chrono::seconds(2))) {
        std::cout << "Thread " << id << " acquired the lock using try_lock_for()." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lock.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire the lock using try_lock_for()." << std::endl;
    }

    // 尝试在指定时间点前锁定互斥量
    auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
    if (lock.try_lock_until(timeout)) {
        std::cout << "Thread " << id << " acquired the lock using try_lock_until()." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lock.unlock();
    } else {
        std::cout << "Thread " << id << " failed to acquire the lock using try_lock_until()." << std::endl;
    }

    // 正常锁定互斥量
    lock.lock();
    std::cout << "Thread " << id << " acquired the lock using lock()." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 检查是否拥有互斥量的锁定
    if (lock.owns_lock()) {
        std::cout << "Thread " << id << " owns the lock." << std::endl;
    }

    // 释放 unique_lock 对互斥量的所有权
    std::timed_mutex* released_mtx = lock.release();
    if (!lock.owns_lock()) {
        std::cout << "Thread " << id << " released the lock using release()." << std::endl;
    }

    // 手动解锁互斥量
    released_mtx->unlock();
}

int main() {
    // 创建两个线程
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

    // 等待线程完成
    t1.join();
    t2.join();

    return 0;
}


http://www.kler.cn/a/572872.html

相关文章:

  • DeepSeek开源周:五大创新项目详解
  • 自定义wordpress三级导航菜单代码
  • FPGA——4位全加器及3-8译码器的实现
  • 2025东方财富笔试考什么?cata能力测评攻略|答题技巧真题分享
  • STM32 两个单片机之间的通信
  • Predix:工业互联网浪潮中的领航者与破局者(工业4.0的长子)
  • SpringTask 引起的错误
  • Linux--基础命令3
  • <Rust><iced>基于rust使用iced构建GUI实例:图片浏览器
  • 安全检查之springboot 配置加密
  • 十大经典排序算法简介
  • nginx 配置403页面(已亲测)
  • leetcode 1328. 破坏回文串 中等
  • Minix OS的配置 SSH C程序编译
  • 网络安全中分区分域
  • 001.words and phrases
  • 【Java 基础(人话版)】Java 虚拟机(JVM)
  • 创建自定义的Spingboot启动器
  • 基于云部署DeepSeek自动分析整合Dou音爆款视频数据
  • Session ID 和 Cookie 的配合机制