C++线程同步之互斥锁

C++线程同步之互斥锁

C++11 中的互斥锁

C++11引入了更现代的多线程编程支持,包括线程库和相关的同步原语。互斥锁(mutex)是用来确保同一时刻只有一个线程能够访问共享资源的一种机制,防止多个线程同时访问共享资源引起的竞态条件。

C++11标准中,互斥锁的实现主要通过以下类来提供:

  1. std::mutexstd::mutex 是最基本的互斥锁类型,用来保证在同一时刻只有一个线程能够锁住它,其他线程在尝试锁时会被阻塞,直到该互斥锁被解锁。
  2. std::recursive_mutexstd::recursive_mutex 是一种递归互斥锁,允许同一线程多次锁住同一个互斥锁,而不会发生死锁。通常适用于需要多次进入同一临界区的场景。
  3. std::timed_mutexstd::timed_mutex 提供了带有超时机制的互斥锁。线程在尝试获取互斥锁时,可以设置一个超时时间,若超过该时间仍未获得锁,则会放弃请求。
  4. std::shared_mutexstd::shared_mutex 允许多个线程以共享方式读访问同一资源,但在写访问时只能有一个线程独占资源。适用于读多写少的场景。

1. std::mutex

std::mutex 是最基本的互斥锁,主要用于保护共享资源,使得多个线程不能同时访问资源。通常通过 lock()unlock() 来进行显式的加锁和解锁,或者使用 std::lock_guardstd::unique_lock 来实现自动锁定和解锁。

使用场景:

  • 适用于普通的互斥保护场景,保证每次只有一个线程能够访问某个资源。

示例代码:

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

std::mutex mtx;

void print(int i){
    std::lock_guard<std::mutex> lock(mtx);  // t1 和  t2 互斥访问
    std::cout<<"thread : "<<i<<std::endl;
}

int main(){
    std::thread t1(print,1);
    std::thread t2(print,2);

    t1.join();
    t2.join();

    return 0;
}

===========输出===============
thread : 1
thread : 2

注意事项:

  • 尽量减少临界区的代码量:加锁的代码部分要尽量精简,减少锁持有的时间,避免阻塞其他线程。
  • 使用 std::lock_guardstd::unique_lock:避免显式调用 lock()unlock(),因为手动解锁可能出现错误。

2. std::recursive_mutex

std::recursive_mutex 是一种递归互斥锁,允许同一线程多次加锁同一个锁,而不会发生死锁。适用于需要多次进入同一临界区的场景。

使用场景:

  • 适用于递归调用或需要在同一线程内多次获取同一锁的情况。

代码示例:

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

std::recursive_mutex mtx;
void recursiveFunction(int leve1){
    if (leve1) {
        std::lock_guard<std::recursive_mutex>  lock(mtx);
        std::cout << "recursiveFunction(" << leve1 << ")\n";
        recursiveFunction(leve1 - 1);
    }
}

int main(){
    std::thread t1(recursiveFunction,3);
    t1.join();
    return 0;
}

==============输出=============
recursiveFunction(3)
recursiveFunction(2)
recursiveFunction(1)

注意事项:

  • 避免滥用递归锁:递归锁只应在需要递归的情况下使用,过度使用可能导致程序的复杂性增加。
  • 不要无限递归:递归调用时一定要有终止条件,否则可能会导致死锁或栈溢出。
  • 性能问题:递归锁的性能开销高于普通锁,因为它需要维护更多的状态信息。

3. std::timed_mutex

std::timed_mutex 提供了带有超时机制的互斥锁,允许线程在获取锁时指定一个超时。如果在指定时间内未能获得锁,则返回失败,防止线程一直阻塞。

std::timed_mutexstd::_mutex多了两个成员函数:try_lock_for()try_lock_until()

try_lock_for函数是当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度

try_lock_until函数是当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点

关于两个函数的返回值:当得到互斥锁的所有权之后,函数会马上解除阻塞,返回true,如果阻塞的时长用完或者到达指定的时间点之后,函数也会解除阻塞,返回false

使用场景:

  • 适用于需要等待一定时间后自动放弃获取锁的场景,例如在多线程竞争资源时,需要避免死锁或长时间阻塞。

代码示例:

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

std::timed_mutex mtx;
void tryLockWithTimeout(int i) {
    if (mtx.try_lock_for(std::chrono::milliseconds(100))) {  // 尝试获取锁,最多等待100毫秒 , 超时则返回false
        std::cout << "Thread " << i << " acquired lock.\n";
        std::this_thread::sleep_for(std::chrono::seconds(5));  // 模拟一些工作
        mtx.unlock();
    } else {
        std::cout << "Thread " << i << " failed to acquire lock within timeout.\n";
    }
}

int main(){
     std::thread t1(tryLockWithTimeout, 1);
    std::thread t2(tryLockWithTimeout, 2);
    
    t1.join();
    t2.join();
    return 0;
}
=======================输出=========================
Thread 1 acquired lock.
Thread 2 failed to acquire lock within timeout.

注意事项:

  • 合理设置超时时间:超时时间要根据业务需求来设置,过短可能导致线程频繁失败,过长可能丧失超时机制的意义。
  • 避免频繁失败:如果多个线程频繁获取锁失败,可能导致性能下降。可以考虑增加重试逻辑。

4. std::shared_mutex

std::shared_mutex 是 C++17 引入的一种共享互斥锁,允许多个线程同时读访问共享资源,但在写访问时,只有一个线程能够访问资源。它适用于读多写少的场景。

使用场景:

  • 适用于读多写少的场景,多个线程可以同时读取数据,但当某个线程需要写数据时,必须独占访问资源。
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <mutex>

std::shared_mutex smtx;
std::vector<int> data;

void readData(int id) {
    std::shared_lock<std::shared_mutex> lock(smtx);  // 共享锁
    std::cout << "Thread " << id << " is reading data.\n";
    if (data.empty()) {
        return;
    }
    for (int i = 0; i < data.size(); ++i) {
        std::cout << data[i] << " ";
    }
    std::cout << "\n";
}

void writeData(int id, int value) {
    std::unique_lock<std::shared_mutex> lock(smtx);  // 独占锁
    std::cout << "Thread " << id << " is writing data." << " value: " << value << "\n";
    data.push_back(value);
}

int main() {
    std::thread threads[20];
    for (int i  = 0; i<20; ++i) {
        int randomValue = rand() % 100; 
        if (i%2 == 0) {
            threads[i] = std::thread(writeData, i, randomValue);
        } else {
            threads[i] = std::thread(readData, i);
        }
    }

    for (int i = 0; i < 20; ++i) {
        threads[i].join();
    }
    
    return 0;
}
===========输出==============
Thread 0 is writing data. value: 83
Thread 1 is reading data.
83 
Thread 3 is reading data.
83 
Thread 2 is writing data. value: 77
Thread 5 is reading data.
83 77 
Thread 4 is writing data. value: 93
Thread 6 is writing data. value: 86
Thread 7 is reading data.
83 77 93 86 
Thread 8 is writing data. value: 49
Thread 9 is reading data.
83 77 93 86 49 
Thread 10 is writing data. value: 62
Thread 11 is reading data.
83 77 93 86 49 62 
Thread 12 is writing data. value: 90
Thread 13 is reading data.
83 77 93 86 49 62 90 
Thread 14 is writing data. value: 63
Thread 15 is reading data.
83 77 93 86 49 62 90 63 
Thread 17 is reading data.
83 77 93 86 49 62 90 63 
Thread 16 is writing data. value: 40
Thread 18 is writing data. value: 72
Thread 19 is reading data.
83 77 93 86 49 62 90 63 40 72 

注意事项:

  • 读写冲突:只有在没有其他线程正在写数据时,才允许多个线程并行读数据。写操作会阻塞所有读操作,因此在高并发环境中要注意读写的平衡。
  • 适用场景:读多写少的场景是 std::shared_mutex 最佳的使用场景,能够有效提高性能。

总结

1. std::mutex
  • 使用场景:保护共享资源,避免多个线程同时访问。
  • 注意点:避免手动加锁解锁带来的错误,推荐使用 std::lock_guardstd::unique_lock
2. std::recursive_mutex
  • 使用场景:适用于递归调用的场景,允许同一线程多次锁定同一资源。
  • 注意点:避免滥用递归锁,确保递归调用的终止条件。
3. std::timed_mutex
  • 使用场景:需要超时控制的场景,避免线程长时间阻塞。
  • 注意点:合理设置超时时间,避免线程频繁失败。
4. std::shared_mutex
  • 使用场景:读多写少的场景,多个线程可以共享读取资源,写操作需要独占。
  • 注意点:读写冲突要避免,保证写操作能够及时完成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liknana

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值