• C++之互斥锁、读写锁、互斥量、 信号量、原子锁机制总结(二百二十五)


    简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!

    优质专栏:Audio工程师进阶系列原创干货持续更新中……】🚀

    人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

    更多原创,欢迎关注:Android系统攻城狮

    欢迎关注Android系统攻城狮

    1.前言

    本篇目的:C++之互斥锁、读写锁、互斥量、 信号量、原子锁机制用法.

    互斥锁、读写锁、互斥量、信号量和原子锁都是并发编程中常用的同步机制。

    1. 互斥锁(Mutex Lock)是最常见的同步机制之一,它用于保护共享资源,只允许一个线程访问资源,其他线程需要等待。互斥锁有两种状态:加锁和解锁。当一个线程获得了互斥锁并执行代码时,其他线程会被阻塞,直到锁被释放。通过互斥锁可以避免多个线程同时访问共享资源而导致的数据竞争问题。

    2. 读写锁(Read-Write Lock)允许多个线程同时读取共享资源,但只有一个线程可以写入资源。读写锁在读取操作频繁、写入操作较少的场景下可以提高并发性能。当有线程正在写入资源时,其他线程无法读取或写入资源,读写锁会保持写锁的状态,直到写操作完成。

    3. 互斥量(Mutex)和互斥锁类似,也用于保护共享资源。互斥量是一种更通用的同步机制,可以实现更复杂的同步逻辑。与互斥锁不同的是,互斥量可以由多个线程共享。

    4. 信号量(Semaphore)是一种计数器,用于控制对共享资源的访问。它可以控制同时访问资源的线程数量,并提供了同步和互斥的机制。信号量有两种操作:P操作(等待)和V操作(释放)。当信号量的计数器为0时,执行P操作的线程会被阻塞,直到计数器大于0时才能继续执行。

    5. 原子锁(Atomic Lock)是一种基于原子操作的同步机制,用于保护对共享资源的访问。原子操作是指不会被其他线程中断的操作。原子锁能够确保在同一时刻只有一个线程可以执行临界区代码,从而避免了竞态条件的发生。

    2.应用实例

    <1>. 互斥锁(Mutex Lock)的作用是保护共享资源,确保在同一时间只有一个线程可以访问该资源。

    #include 
    #include 
    
    std::mutex mtx;
    
    void sharedResourceAccess() {
        // 锁定互斥锁,确保只有一个线程可以进入临界区
        mtx.lock();
    
        // 访问共享资源的代码
        // ...
    
        // 解锁互斥锁,允许其他线程访问共享资源
        mtx.unlock();
    }
    
    int main() {
        // 创建多个线程并发访问共享资源
        // ...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 在C++中,互斥锁(Mutex)是一种用来实现线程同步的机制,用于保护临界区(Critical Section)的同步访问。互斥锁的原理是通过对共享资源加锁来确保同时只有一个线程可以访问该资源。

    • 互斥锁的使用包括两个主要步骤:加锁和解锁。当一个线程需要访问临界区时,会先尝试加锁,如果成功获取到锁,则可以进入临界区执行操作。在临界区执行完毕后,线程会释放锁,允许其他线程获取锁并进入临界区。

    <2>. 读写锁(Read-Write Lock)的作用是允许多个线程同时读取共享资源,但只有一个线程可以写入资源。

    #include 
    #include 
    
    std::shared_mutex rwMutex;
    
    void readAccess() {
        // 读操作,可以同时由多个线程进入临界区
        std::shared_lock<std::shared_mutex> readLock(rwMutex);
    
        // 读取共享资源的代码
        // ...
    }
    
    void writeAccess() {
        // 写操作,只有一个线程可以进入临界区
        std::unique_lock<std::shared_mutex> writeLock(rwMutex);
    
        // 写入共享资源的代码
        // ...
    }
    
    int main() {
        // 创建多个线程并发读写共享资源
        // ...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • C++中的读写锁(Read-Write Lock)是一种用于控制对共享资源的并发访问的锁机制。相比于互斥锁,在读写锁中允许并发地执行多个读操作,但在写操作时需要独占访问。

    • 读写锁的原理是根据访问类型(读操作或写操作)来决定锁的状态和行为。它的主要特点包括:

    1. 多个读操作可以同时进行,互不干扰。当没有线程在写操作时,允许多个线程同时获取读锁。
    2. 写操作需要独占地访问,此时不允许其他读操作或写操作。
    3. 当有线程持有读锁时,写锁将一直处于阻塞状态,直到所有持有的读锁都被释放。### <3>. 互斥量(Mutex)的作用与互斥锁类似,也用于保护共享资源。互斥量可以由多个线程共享,其用法与互斥锁类似。

    <3>.互斥量

    #include 
    #include 
    
    std::mutex mtx;
    
    void sharedResourceAccess() {
        // 锁定互斥量,确保只有一个线程可以进入临界区
        std::lock_guard<std::mutex> lock(mtx);
    
        // 访问共享资源的代码
        // ...
    }
    
    int main() {
        // 创建多个线程并发访问共享资源
        // ...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • C++中的互斥量(Mutex)是一种用于实现互斥访问的锁机制,用来保护共享资源免受并发访问的干扰。

    • 互斥量的原理是基于一个简单的概念,即同一时间只有一个线程可以持有该互斥量的锁。当一个线程获取了互斥量的锁之后,其他线程就无法获取该互斥量的锁,只能等待锁的释放。

    互斥量实现互斥访问的方式可以分为两类:

    1. 阻塞式:当一个线程尝试获取互斥量的锁,而锁已经被其他线程获取时,该线程会被阻塞,直到锁被释放。这样可以确保同一时间只有一个线程对共享资源进行访问,但存在可能产生死锁的风险。

    2. 非阻塞式:当一个线程尝试获取互斥量的锁,如果锁已经被其他线程获取,则该线程会得到一个错误码或标识,而不会被阻塞。通过不断地尝试获取锁的方式,线程可以在后续的时刻再次尝试获取锁。

    使用互斥量可以确保在单个时间点只有一个线程可以访问共享资源,从而避免了并发访问导致的数据竞争和不确定性。在使用互斥量时,需要谨慎选择锁的粒度和获取锁的时机,以最大程度地减少锁的竞争和阻塞时间,提高程序的并发性和性能。

    <4>. 信号量(Semaphore)的作用是控制对共享资源的访问,限制同时访问资源的线程数量。

    #include 
    #include 
    #include 
    #include 
    
    std::mutex mtx;
    std::condition_variable cv;
    int counter = 0;
    
    void accessResource() {
        std::unique_lock<std::mutex> lock(mtx);
    
        // 等待信号量,当 counter 大于 0 时继续执行
        cv.wait(lock, [] { return counter > 0; });
    
        // 访问共享资源的代码
        // ...
    
        // 释放信号量,计数器减一
        counter--;
    }
    
    void releaseResource() {
        std::unique_lock<std::mutex> lock(mtx);
    
        // 增加信号量,计数器加一
        counter++;
    
        // 通知等待的线程可以继续执行
        cv.notify_one();
    }
    
    int main() {
        // 创建多个线程并发访问共享资源
        // ...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • C++中的信号量(Semaphore)是一种用于控制并发访问的同步机制,用来管理对共享资源的访问权限。

    • 信号量的原理是基于一个计数器和等待队列。信号量内部维护一个整型计数器,用于记录可用资源的数量。当线程需要访问共享资源时,它会尝试获取一个资源,即将信号量的计数器减一。如果计数器大于等于零,则线程成功获取了一个资源,并可以继续执行;如果计数器小于零,则表示资源已被占用,该线程需要等待。

    当一个线程使用完资源后,它会释放掉该资源,并将信号量的计数器加一。如果此时有其他线程正在等待资源,那么其中一个线程将被唤醒,并可以继续执行。

    信号量的计数器可以表示为正整数,也可以是非负整数。在使用时,信号量的初始值可以根据需求设置。如果初始值为1,则信号量可以用作互斥锁;如果初始值大于1,则信号量可以用作限制并发访问的资源数。

    信号量可以用于解决一些典型的并发访问问题,如有限缓冲区的生产者消费者问题、并发任务的调度和同步等。

    <5>.原子锁,用于保护对共享资源的访问,原子操作是指不会被其他线程中断的操作。

    #include 
    #include 
    #include 
    
    // 创建一个原子锁
    std::atomic<bool> atomic_lock(false);
    
    void criticalSection() {
        while (atomic_lock.exchange(true)) {
            // 原子锁正在被其他线程使用,等待...
        }
    
        // 进入临界区,访问共享资源
        std::cout << "进入临界区,访问共享资源" << std::endl;
    
        // 模拟一段临界区代码
        std::this_thread::sleep_for(std::chrono::seconds(2));
    
        // 退出临界区,释放原子锁
        atomic_lock.store(false);
        std::cout << "退出临界区,释放原子锁" << std::endl;
    }
    
    int main() {
        // 创建多个线程并发访问临界区
        std::thread t1(criticalSection);
        std::thread t2(criticalSection);
    
        // 等待线程完成
        t1.join();
        t2.join();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • C++中的原子锁是一种用于实现线程安全的同步机制,也称为原子操作。它提供了一种保证特定代码区域在任意时间点只能被一个线程执行的能力。

    • 原子锁的实现原理是基于硬件的原子操作指令或操作系统提供的原子操作接口。通过这些机制,可以确保在多线程环境下,对共享资源的访问是原子性、无竞争的。

    • 原子锁的一种常见实现方式是使用互斥量(Mutex)。当一个线程尝试获取原子锁时,如果锁已经被其他线程获取,则该线程会被阻塞,直到锁被释放。这样可以确保在任意时间点只有一个线程可以执行被保护的代码区域。

    • 原子锁还可以采用其他实现方式,如使用硬件原子操作指令或操作系统提供的原子操作接口。这些方式在底层实现上比互斥量更高效,减少了阻塞和唤醒线程的开销。

    • 原子锁的使用可以提供更细粒度的同步控制,避免了大范围的阻塞,从而提高并发性和性能。然而,在使用原子锁时需要注意避免死锁和资源竞争的问题。

    • 过度使用原子锁可能会导致性能下降,因为它们在竞争激烈的情况下可能会导致线程阻塞和唤醒的频繁切换。因此,在设计多线程程序时,需要综合考虑并发性、性能和代码复杂性等因素,选择合适的同步机制来保护共享资源的访问。

    3.自定义实现Mutex自动加锁和解锁

    #include 
    #include 
    
    class Mutex {
    public:
      void lock() {
        // 实现互斥锁的加锁操作
        std::cout << "Mutex locked" << std::endl;
      }
    
      void unlock() {
        // 实现互斥锁的解锁操作
        std::cout << "Mutex unlocked" << std::endl;
      }
    };
    
    class Autolock {
    public:
      explicit Autolock(Mutex& mutex) : mLock(mutex) {
        mLock.lock();
      }
    
      ~Autolock() {
        mLock.unlock();
      }
    
    private:
      Mutex& mLock;
    };
    
    int main() {
      Mutex mutex;
      {
        Autolock autolock(mutex);  // 创建Autolock对象,自动加锁
        // 执行在临界区内的操作
        std::cout << "In critical section" << std::endl;
      }  // Autolock对象销毁,自动解锁
    
      return 0;
    }
    
    ```
    `注意:使用构造函数和析构函数实现Mutex自动加锁和解锁。`
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    【HCIP】OSPF 特殊区域、汇总、认证
    JavaScript基础07——变量拓展-数组
    Elasticsearch:使用 function_score 中的weight和gauss衰减函数定制搜索结果的分数
    一文讲清楚网络安全是什么?网络安全工程师需要学什么?就业前景如何?
    类和对象学习笔记
    L2-052 吉利矩阵
    机器学习(二十一):类不平衡处理之权重法
    音响是如何把微弱声音放大呢
    PyTorch: 计算图与动态图机制
    Windows系统中Apache Http服务器简单使用
  • 原文地址:https://blog.csdn.net/u010164190/article/details/133089661