很久之前,我就相关内容写了一篇博客。今日,重读相关内容,还是觉得自己要就相关内容,再写一篇。不为别的,权当加深印象之用吧。C++11 提供了标准化的多线程支持,其中包含四种不同类型的互斥锁,用于保护共享资源并确保线程安全。这次,我将详细介绍 C++11 提供的四种互斥锁及其使用场景,并附上相关代码示例。


C++11 提供的四种互斥锁

  1. mutex:标准互斥锁,用于控制对共享资源的独占访问。
  2. timed_mutex:带超时机制的互斥锁,支持在指定时间内尝试加锁。
  3. recursive_mutex:递归互斥锁,允许同一线程多次锁定资源,防止递归函数加锁时发生死锁。
  4. recursive_timed_mutex:带超时机制的递归互斥锁,结合了递归锁和超时机制。
1
2
#include <mutex>  // 包含互斥锁类的头文件
#include <thread> // 包含线程类的头文件

一、mutex 类 —— 标准互斥锁

1.1 lock() —— 加锁操作

lock() 函数用于申请互斥锁,线程会尝试获取锁的所有权。如果锁已经被另一个线程持有,则当前线程会阻塞,直到锁可用。

1.2 unlock() —— 解锁操作

unlock() 函数用于释放锁的所有权,其他等待锁的线程可以继续获取该锁。

1.3 try_lock() —— 尝试加锁

try_lock() 函数不阻塞,立即尝试获取锁。如果锁是空闲的,则成功加锁并返回 true;如果锁已经被其他线程持有,则返回 false

示例代码:使用 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
#include <iostream>
#include <thread>
#include <mutex> // 互斥锁类的头文件

using namespace std;

mutex mtx; // 创建互斥锁,保护共享资源 cout 对象

// 普通函数,模拟多线程环境下的资源访问
void func(int bh, const string& str) {
    for (int ii = 1; ii <= 10; ii++) {
        mtx.lock(); // 加锁,保护共享资源
        cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
        mtx.unlock(); // 解锁,释放共享资源
        this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒
    }
}

int main() {
    // 创建多个线程,每个线程访问共享资源 cout
    thread t1(func, 1, "我是一只傻傻鸟。");
    thread t2(func, 2, "我是一只傻傻鸟。");
    thread t3(func, 3, "我是一只傻傻鸟。");
    thread t4(func, 4, "我是一只傻傻鸟。");
    thread t5(func, 5, "我是一只傻傻鸟。");

    // 等待所有线程完成
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
}

代码解析

在这个例子中,多个线程同时访问共享的 cout 对象输出信息。为了防止多个线程同时访问共享资源而造成数据竞争,使用了 mtx.lock() 加锁和 mtx.unlock() 解锁来保护 cout

二、timed_mutex 类 —— 带超时机制的互斥锁

timed_mutex 提供了 try_lock_for()try_lock_until() 两个函数,允许线程在指定的时间段内尝试获取锁。如果超过指定时间锁依然被其他线程持有,则加锁失败。

  • try_lock_for(时间长度):尝试在指定时间长度内获取锁。
  • try_lock_until(时间点):尝试在指定时间点之前获取锁。

示例代码:使用 timed_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
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

using namespace std;

timed_mutex tmtx; // 创建带超时机制的互斥锁

void func(int bh, const string& str) {
    for (int ii = 1; ii <= 5; ii++) {
        if (tmtx.try_lock_for(chrono::milliseconds(100))) { // 尝试加锁,最多等待 100 毫秒
            cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
            tmtx.unlock(); // 解锁
        } else {
            cout << "第" << ii << "次尝试加锁失败,线程" << bh << "放弃了操作。" << endl;
        }
        this_thread::sleep_for(chrono::milliseconds(150)); // 休眠 150 毫秒
    }
}

int main() {
    thread t1(func, 1, "我是一只傻傻鸟。");
    thread t2(func, 2, "我有一只小小鸟。");

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

代码解析

在此示例中,每个线程尝试使用 timed_mutex 加锁。如果在 100 毫秒内加锁成功,线程会输出信息;如果锁定失败,线程会跳过此次操作,继续执行下一轮。

三、recursive_mutex 类 —— 递归互斥锁

recursive_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
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

class AA {
    recursive_mutex rmtx; // 创建递归互斥锁
public:
    void func1() {
        rmtx.lock();
        cout << "调用了 func1()\n";
        rmtx.unlock();
    }

    void func2() {
        rmtx.lock();
        cout << "调用了 func2()\n";
        func1(); // 在 func2 内部调用 func1
        rmtx.unlock();
    }
};

int main() {
    AA aa;
    aa.func2(); // 调用 func2,它会递归调用 func1
}

代码解析

递归互斥锁允许同一线程在不解锁的情况下多次加锁,这对递归调用非常有用。在这个例子中,func2() 调用了 func1(),但两者都使用了相同的递归互斥锁,因此不会导致死锁。

四、lock_guard 类 —— RAII 风格的加锁管理

lock_guard 是一个模板类,它采用 RAII(资源获取即初始化)思想来简化互斥锁的使用。当 lock_guard 对象被创建时,它自动加锁;当 lock_guard 对象超出作用域时,它会自动解锁,这避免了手动加锁/解锁带来的潜在错误。

示例代码:使用 lock_guard 简化加锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

mutex mtx; // 创建互斥锁

void func(int bh, const string& str) {
    for (int ii = 1; ii <= 5; ii++) {
        lock_guard<mutex> lg(mtx); // 自动加锁,在作用域结束时自动解锁
        cout << "第" << ii << "次表白:亲爱的" << bh << "号," << str << endl;
        this_thread::sleep_for(chrono::seconds(1)); // 休眠 1 秒
    }
}

int main() {
    thread t1(func, 1, "我是一只傻傻鸟。");
    thread t2(func, 2, "我有一只小小鸟。");

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

代码解析

在这个示例中,lock_guard 自动处理互斥锁的加锁和解锁,确保每次加锁操作都能被正确管理,从而避免手动解锁时可能出现的错误。


总结

C++11 提供的四种互斥锁为多线程编程提供了强大的支持。通过使用 mutextimed_mutexrecursive_mutexlock_guard,开发者能够在多线程环境下有效保护共享资源,防止数据竞争。同时,lock_guard 的 RAII 机制大大简化了锁的使用,减少了手动加锁和解锁的错误风险。