c++多线程2

244 阅读5分钟

一、mutex

mutex(互斥锁)是 C++ 标准库中用于实现线程同步的重要工具,定义在 <mutex> 头文件中。它的主要作用是保护共享资源,防止多个线程同时访问共享资源而导致的数据竞争(Race Condition)问题。


1. mutex 的基本概念

  • 互斥锁mutex 是一种同步原语,用于确保同一时间只有一个线程可以访问共享资源。
  • 锁的机制
    • 当一个线程持有锁时,其他线程必须等待锁被释放后才能获取锁。
    • 锁的获取和释放是成对出现的,确保资源的安全访问。

2. mutex 的常用方法

  • lock():尝试获取锁。如果锁已被其他线程持有,则当前线程阻塞,直到锁被释放。
  • unlock():释放锁,允许其他线程获取锁。
  • try_lock():尝试获取锁,如果锁已被其他线程持有,则立即返回 false,不会阻塞。

3. mutex 的基本用法

以下是一个简单的示例,展示了如何使用 mutex 保护共享资源:

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

mutex mtx; // 定义互斥锁
int sharedData = 0; // 共享资源

void increment() {
    for (int i = 0; i < 10000; ++i) {
        mtx.lock(); // 加锁
        ++sharedData; // 操作共享资源
        mtx.unlock(); // 解锁
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Shared data: " << sharedData << endl; // 输出 20000
    return 0;
}

4. lock_guardunique_lock

手动调用 lock()unlock() 容易出错(如忘记解锁),因此 C++ 提供了 lock_guardunique_lock自动管理锁的生命周期

(1)lock_guard
  • 是一个 RAII 风格的锁管理器,构造时自动加锁,析构时自动解锁
  • 适用于简单的加锁场景。

示例:

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

mutex mtx;
int sharedData = 0;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        lock_guard<mutex> lock(mtx); // 自动加锁和解锁
        ++sharedData;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Shared data: " << sharedData << endl; // 输出 20000
    return 0;
}
(2)unique_lock
  • lock_guard 更灵活,支持手动加锁和解锁。
  • 可以延迟加锁、转移锁的所有权等。

示例:

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

mutex mtx;
int sharedData = 0;

void increment() {
    for (int i = 0; i < 10000; ++i) {
        unique_lock<mutex> lock(mtx); // 自动加锁和解锁
        ++sharedData;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Shared data: " << sharedData << endl; // 输出 20000
    return 0;
}

5. try_lock 的使用

try_lock() 尝试获取锁,如果锁已被其他线程持有,则立即返回 false,不会阻塞。

示例:

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

mutex mtx;

void task() {
    if (mtx.try_lock()) { // 尝试获取锁
        cout << "Thread " << this_thread::get_id() << " acquired the lock." << endl;
        this_thread::sleep_for(chrono::seconds(1)); // 模拟耗时操作
        mtx.unlock(); // 释放锁
    } else {
        cout << "Thread " << this_thread::get_id() << " failed to acquire the lock." << endl;
    }
}

int main() {
    thread t1(task);
    thread t2(task);
    t1.join();
    t2.join();
    return 0;
}

6. 死锁问题

死锁是指多个线程互相等待对方释放锁,导致程序无法继续执行。常见的死锁场景包括:

  • 多个线程以不同的顺序获取锁。
  • 线程持有锁的同时再次尝试获取锁。

示例:死锁

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

mutex mtx1, mtx2;

void task1() {
    mtx1.lock();
    this_thread::sleep_for(chrono::milliseconds(100)); // 模拟耗时操作
    mtx2.lock(); // 等待 mtx2
    cout << "Task 1 completed." << endl;
    mtx2.unlock();
    mtx1.unlock();
}

void task2() {
    mtx2.lock();
    this_thread::sleep_for(chrono::milliseconds(100)); // 模拟耗时操作
    mtx1.lock(); // 等待 mtx1
    cout << "Task 2 completed." << endl;
    mtx1.unlock();
    mtx2.unlock();
}

int main() {
    thread t1(task1);
    thread t2(task2);
    t1.join();
    t2.join();
    return 0;
}

解决方法

  • 按固定顺序加锁。
  • 使用 std::lock() 一次性锁定多个互斥锁。

示例:避免死锁

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

mutex mtx1, mtx2;

void task1() {
    lock(mtx1, mtx2); // 一次性锁定多个互斥锁
    lock_guard<mutex> lock1(mtx1, adopt_lock); // 接管 mtx1
    lock_guard<mutex> lock2(mtx2, adopt_lock); // 接管 mtx2
    cout << "Task 1 completed." << endl;
}

void task2() {
    lock(mtx1, mtx2); // 一次性锁定多个互斥锁
    lock_guard<mutex> lock1(mtx1, adopt_lock); // 接管 mtx1
    lock_guard<mutex> lock2(mtx2, adopt_lock); // 接管 mtx2
    cout << "Task 2 completed." << endl;
}

int main() {
    thread t1(task1);
    thread t2(task2);
    t1.join();
    t2.join();
    return 0;
}

7. recursive_mutex

  • 允许同一个线程多次获取同一个锁。
  • 适用于递归调用场景。

示例:

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

recursive_mutex mtx;

void recursiveFunction(int n) {
    if (n <= 0) return;
    mtx.lock();
    cout << "Locked by thread " << this_thread::get_id() << ", n = " << n << endl;
    recursiveFunction(n - 1); // 递归调用
    mtx.unlock();
}

int main() {
    thread t(recursiveFunction, 3);
    t.join();
    return 0;
}

二、wait

wait 是 C++ 中用于线程同步的重要机制,通常与条件变量(std::condition_variable)一起使用。它的作用是让当前线程等待某个条件满足,直到被其他线程通知(notify)或超时。


1. wait 的基本概念

  • 条件变量std::condition_variable 是一种同步原语,用于线程间的通信。它允许一个或多个线程等待某个条件成立。

  • wait 的作用

    • 让当前线程进入等待状态,直到被其他线程唤醒。
    • 在等待期间,线程会释放锁(避免死锁),并在被唤醒后重新获取锁。

2. wait 的常用形式

std::condition_variable 提供了两种 wait 方法:

  1. wait(lock)

    • 让当前线程等待,直到被 notify_one() 或 notify_all() 唤醒。
    • 需要传入一个 std::unique_lock<std::mutex> 对象。
  2. wait(lock, predicate)

    • 让当前线程等待,直到条件满足(predicate 返回 true)。
    • 避免虚假唤醒(Spurious Wakeup)。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

mutex mtx;
condition_variable cv;
bool ready = false; // 条件变量

void waitForReady() {
    unique_lock<mutex> lock(mtx);
    cv.wait(lock, [] { return ready; }); // 等待 ready 为 true
    cout << "Thread is ready!" << endl;
}

void setReady() {
    this_thread::sleep_for(chrono::seconds(2)); // 模拟准备工作
    {
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 通知等待的线程
}

int main() {
    thread t1(waitForReady);
    thread t2(setReady);
    t1.join();
    t2.join();
    return 0;
}

3.wait 的工作流程

  1. 线程调用 wait释放锁并进入等待状态
  2. 其他线程修改条件并调用 notify_one() 或 notify_all()
  3. 等待的线程被唤醒,重新获取锁并检查条件。
  4. 如果条件满足,线程继续执行;否则,再次进入等待状态。

4.wait_for 和 wait_until

std::condition_variable 还提供了超时版本的 wait

  • wait_for(lock, duration) :等待一段时间,超时后返回。
  • wait_until(lock, time_point) :等待到某个时间点,超时后返回。

示例:wait_for 的使用

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

mutex mtx;
condition_variable cv;
bool ready = false;

void waitForReady() {
    unique_lock<mutex> lock(mtx);
    if (cv.wait_for(lock, chrono::seconds(3), [] { return ready; })) {
        cout << "Thread is ready!" << endl;
    } else {
        cout << "Timeout!" << endl;
    }
}

void setReady() {
    this_thread::sleep_for(chrono::seconds(5)); // 模拟准备工作
    {
        lock_guard<mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 通知等待的线程
}

int main() {
    thread t1(waitForReady);
    thread t2(setReady);
    t1.join();
    t2.join();
    return 0;
}