示例详解C++中的各种锁

 更新时间:2024年11月06日 08:53:45   作者:Arthurian  
C++中常见的锁包括互斥锁、递归互斥锁、读写锁、定时互斥锁、递归定时互斥锁、自旋锁和条件变量,互斥锁用于防止多线程同时访问共享资源,递归互斥锁允许同一线程多次获取锁,读写锁区分读写操作,提高并发性

在多线程开发中,经常会遇到数据同步,很多情况下用锁都是一个很好的选择。C++中常用的锁主要有下面几种:

互斥锁(std::mutex)

  • 这是最基本的一种锁。它用于保护共享资源,在任意时刻,最多只有一个线程可以获取该锁,从而访问被保护的资源。当一个线程获取了互斥锁后,其他试图获取该锁的线程会被阻塞,直到持有锁的线程释放它。
  • 例如,在一个多线程程序中,如果多个线程需要访问和修改同一个全局变量,就可以使用互斥锁来确保在同一时间只有一个线程能够进行修改操作,避免数据竞争导致的错误结果。
#include <iostream>
#include <mutex>
#include <thread>
std::mutex m;
int counter = 0;
void increment() {
    m.lock();
    counter++;
    std::cout << "Counter value in thread " << std::this_thread::get_id() << " is " << counter << std::endl;
    m.unlock();
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    return 0;
}

递归互斥锁(std::recursive_mutex)

  • 递归互斥锁允许同一个线程多次获取该锁。它内部会记录锁的获取次数,每获取一次,计数加 1,每次释放锁时,计数减 1,当计数为 0 时,锁才真正被释放,可供其他线程获取。
  • 假设在一个复杂的函数调用链中,函数 A 调用函数 B,函数 B 又调用函数 A,并且这些函数都需要访问同一个受保护的资源。如果使用普通互斥锁,就会出现死锁,而递归互斥锁就可以避免这种情况,因为它允许同一线程多次获取锁。
#include <iostream>
#include <mutex>
#include <thread>
std::recursive_mutex rm;
void recursiveFunction(int count) {
    rm.lock();
    if (count > 0) {
        std::cout << "Recursive call with count = " << count << std::endl;
        recursiveFunction(count - 1);
    }
    rm.unlock();
}
int main() {
    std::thread t(recursiveFunction, 3);
    t.join();
    return 0;
}

读写锁(std::shared_mutex) C++17开始才有

  • 读写锁主要用于区分对共享资源的读操作和写操作。它有两种获取模式:共享模式(读模式)和独占模式(写模式)。
  • 多个线程可以同时以共享模式获取读写锁,这意味着它们可以同时读取共享资源,而不会相互干扰。但是,当一个线程要以独占模式获取读写锁(进行写操作)时,其他线程(无论是读操作还是写操作)都不能获取该锁,直到写操作完成并释放锁。这种机制在有大量读操作和少量写操作的场景下,可以提高程序的并发性能。例如,在一个缓存系统中,多个线程可能经常读取缓存中的数据,只有在缓存数据需要更新时才会进行写操作,使用读写锁可以很好地处理这种情况。 
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>
std::shared_mutex smtx;
int shared_data = 0;
void read_data() {
    std::shared_lock<std::shared_mutex> lock(smtx);
    std::cout << "Read data: " << shared_data << std::endl;
}
void write_data(int new_value) {
    std::unique_lock<std::shared_mutex> lock(smtx);
    shared_data = new_value;
    std::cout << "Wrote data: " << shared_data << std::endl;
}
int main() {
    std::vector<std::thread> read_threads;
    for (int i = 0; i < 5; i++) {
        read_threads.push_back(std::thread(read_data));
    }
    std::thread write_thread(write_data, 10);
    for (auto& t : read_threads) {
        t.join();
    }
    write_thread.join();
    return 0;
}

定时互斥锁(std::timed_mutex)

  • 定时互斥锁是std::mutex的扩展。除了具备std::mutex的基本功能外,它还允许线程在尝试获取锁时设置一个超时时间。
  • 如果在规定的超时时间内无法获取锁,线程不会一直等待,而是可以执行其他操作或者返回错误信息。这在一些对时间敏感的场景中非常有用,比如在一个实时系统中,线程不能因为等待一个锁而无限期地阻塞,需要在一定时间后放弃获取并进行其他处理。 
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
std::timed_mutex tm;
void tryLockFunction() {
    if (tm.try_lock_for(std::chrono::seconds(1))) {
        std::cout << "Acquired lock" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        tm.unlock();
    } else {
        std::cout << "Could not acquire lock in time" << std::endl;
    }
}
int main() {
    std::thread t1(tryLockFunction);
    std::thread t2(tryLockFunction);
    t1.join();
    t2.join();
    return 0;
}

递归定时互斥锁(std::recursive_timed_mutex)

  • 这是结合了递归互斥锁和定时互斥锁特点的一种锁。它允许同一线程多次获取锁,并且在获取锁时可以设置超时时间。
  • 当一个线程多次获取这种锁后,需要释放相同次数的锁,锁才会真正被释放,并且在获取锁的过程中,如果在超时时间内无法获取,线程可以采取相应的措施。 
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
std::recursive_timed_mutex rtm;
void recursiveTryLockFunction(int count) {
    if (rtm.try_lock_for(std::chrono::seconds(1))) {
        std::cout << "Recursive acquired lock, count = " << count << std::endl;
        if (count > 0) {
            recursiveTryLockFunction(count - 1);
        }
        rtm.unlock();
    } else {
        std::cout << "Could not recursively acquire lock in time" << std::endl;
    }
}
int main() {
    std::thread t(recursiveTryLockFunction, 3);
    t.join();
    return 0;
}

自旋锁(通常用std::atomic_flag实现)

  • 自旋锁是一种忙等待的锁机制。当一个线程尝试获取自旋锁而锁已经被占用时,这个线程不会进入阻塞状态,而是会不断地检查(“自旋”)锁是否已经被释放。
  • 自旋锁在等待时间较短的情况下可能会有比较好的性能表现,因为它避免了线程切换的开销。但是,如果等待时间过长,由于线程一直在占用 CPU 资源进行检查,会导致 CPU 资源的浪费。一般在底层代码或者对性能要求极高、等待时间预计很短的场景下使用。 
#include <iostream>
#include <atomic>
#include <thread>
std::atomic_flag spinLock = ATOMIC_FLAG_INIT;
void criticalSection() {
    while (spinLock.test_and_set()) {
        // 自旋等待
    }
    std::cout << "Entered critical section" << std::endl;
    // 临界区操作
    spinLock.clear();
}
int main() {
    std::thread t1(criticalSection);
    std::thread t2(criticalSection);
    t1.join();
    t2.join();
    return 0;
}

条件变量(std::condition_variable)配合互斥锁用于同步(严格来说条件变量不是锁,但常一起用于线程同步场景)

  • 条件变量本身不是一种锁,但它通常和互斥锁一起使用,用于实现线程间的同步。它可以让一个线程等待某个条件满足后再继续执行。
  • 例如,一个生产者 - 消费者模型中,消费者线程在缓冲区为空时可以使用条件变量等待,直到生产者线程生产出产品并通知消费者线程,这个过程中互斥锁用于保护缓冲区这个共享资源,条件变量用于实现线程间的通信和同步。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;
const int bufferSize = 5;
void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        while (buffer.size() == bufferSize) {
            cv.wait(lock);
        }
        buffer.push(i);
        std::cout << "Produced: " << i << std::endl;
        cv.notify_all();
    }
}
void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        while (buffer.empty()) {
            cv.wait(lock);
        }
        int data = buffer.front();
        buffer.pop();
        std::cout << "Consumed: " << data << std::endl;
        cv.notify_all();
    }
}
int main() {
    std::thread producerThread(producer);
    std::thread consumerThread(consumer);
    producerThread.join();
    consumerThread.join();
    return 0;
}

到此这篇关于C++中的各种锁的文章就介绍到这了,更多相关C++ 各种锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 理解数据结构

    理解数据结构

    本文是对数据结构对编程的重要性,数据结构对数据存储,数据处理,内存工作,从宏观上理解数据结构
    2016-07-07
  • 深入理解C/C++中的写时拷贝

    深入理解C/C++中的写时拷贝

    这篇文章主要给大家介绍了C/C++中写时拷贝的相关资料,所谓写时拷贝也就是拖延版的深拷贝,下面文章中介绍的非常清楚,需要的朋友可以参考学习,下面来一起看看吧。
    2017-03-03
  • C++左值引用、右值引用应用场景分析

    C++左值引用、右值引用应用场景分析

    本文给大家介绍C++左值引用、右值引用应用场景分析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2026-03-03
  • 详解C语言整数和浮点数在内存中的存储

    详解C语言整数和浮点数在内存中的存储

    这篇文章主要介绍了C语言整数和浮点数在内存中是如何存储的,文中有详细的代码示例供大家参考,对大家了解学习C语言整数和浮点数在内存中的存储有一定的帮助,需要的朋友可以参考下
    2024-03-03
  • QT中start()和startTimer()的区别小结

    QT中start()和startTimer()的区别小结

    QTimer提供了定时器信号和单触发定时器,本文主要介绍了QT中start()和startTimer()的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-09-09
  • C语言实现学生信息管理系统(链表)

    C语言实现学生信息管理系统(链表)

    这篇文章主要为大家详细介绍了C语言实现学生信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • 详解C语言中getgid()函数和getegid()函数的区别

    详解C语言中getgid()函数和getegid()函数的区别

    这篇文章主要介绍了详解C语言中getgid()函数和getegid()函数的区别,注意getegid只返回有效的组识别码,需要的朋友可以参考下
    2015-08-08
  • C/C++实现双路快速排序算法原理

    C/C++实现双路快速排序算法原理

    这篇文章主要为大家详细介绍了C/C++实现双路快速排序算法原理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-05-05
  • C++遗传算法类文件实例分析

    C++遗传算法类文件实例分析

    这篇文章主要介绍了C++遗传算法的一个类文件,是学习遗传算法的绝佳参考资料,需要的朋友可以参考下
    2014-08-08
  • 详解C++ 中的三种继承方式

    详解C++ 中的三种继承方式

    这篇文章主要介绍了详解C++ 中的三种继承方式,帮助大家更好的理解和学习使用c++,感兴趣的朋友可以了解下
    2021-03-03

最新评论