C++并发编程入门:std::thread与std::mutex的基本用法

C++并发编程入门:std::thread与std::mutex的基本用法

大家好!欢迎来到今天的C++并发编程讲座。今天我们要聊聊两个非常重要的家伙——std::threadstd::mutex。它们就像是并发世界的双胞胎兄弟,一个负责跑腿干活,另一个负责协调秩序。听起来很有趣吧?那咱们就赶紧开始吧!


第一幕:认识std::thread

在C++中,std::thread是创建线程的工具。它就像你雇佣的一个小工人,可以独立完成任务。下面我们来看一个简单的例子:

#include <iostream>
#include <thread>

void sayHello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(sayHello);  // 创建一个线程,执行sayHello函数
    t.join();                 // 等待线程完成
    return 0;
}

运行结果:

Hello from thread!

在这个例子中,我们创建了一个线程t,让它去执行sayHello函数。然后通过t.join()等待线程完成。如果没有join(),主线程可能会提前结束,导致子线程被强制终止。

小贴士: std::thread有两种常用的生命周期管理方法:

  • join():等待线程完成。
  • detach():让线程独立运行,主线程不再关心它的生死。

第二幕:并发的世界需要秩序——std::mutex

好了,现在我们有了多个线程,但如果它们同时访问共享资源(比如一个变量),就会出现问题。这就像是多人同时挤进电梯,结果把电梯卡住了。为了避免这种情况,我们需要一个“秩序维护者”——std::mutex

std::mutex是一个互斥锁,确保同一时间只有一个线程能访问共享资源。下面是一个经典的银行账户示例:

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

std::mutex mtx;  // 定义一个互斥锁
int balance = 0;

void deposit(int amount) {
    mtx.lock();  // 加锁
    balance += amount;
    std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;
    mtx.unlock();  // 解锁
}

int main() {
    std::thread t1(deposit, 100);
    std::thread t2(deposit, 200);

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

    return 0;
}

运行结果(可能):

Deposited 100, new balance: 100
Deposited 200, new balance: 300

在这个例子中,mtx.lock()mtx.unlock()确保了只有当一个线程完成存款操作后,另一个线程才能继续操作。这样就避免了数据竞争问题。

重要提醒: 如果忘记解锁,会导致死锁(Deadlock)。为了防止这种问题,推荐使用std::lock_guard,它可以自动管理锁的生命周期。


第三幕:std::lock_guard登场

std::lock_guard是一个RAII(Resource Acquisition Is Initialization)风格的工具,可以自动管理锁的加锁和解锁。我们来改写上面的例子:

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

std::mutex mtx;
int balance = 0;

void deposit(int amount) {
    std::lock_guard<std::mutex> lock(mtx);  // 自动加锁
    balance += amount;
    std::cout << "Deposited " << amount << ", new balance: " << balance << std::endl;
    // 锁会在lock对象销毁时自动释放
}

int main() {
    std::thread t1(deposit, 100);
    std::thread t2(deposit, 200);

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

    return 0;
}

使用std::lock_guard后,代码更简洁,也不容易出错。即使程序抛出异常,锁也会被正确释放。


第四幕:多线程的常见陷阱

虽然std::threadstd::mutex功能强大,但如果不小心使用,很容易掉进坑里。下面是一些常见的陷阱:

1. 忘记join()detach()

如果你既不调用join()也不调用detach(),程序会崩溃。这是因为std::thread的析构函数会检测线程是否仍在运行,如果发现未处理的线程,就会调用std::terminate()

2. 死锁

如果多个线程同时试图获取多个锁,可能会导致死锁。例如:

std::mutex m1, m2;

void thread1() {
    std::lock_guard<std::mutex> lock1(m1);
    std::lock_guard<std::mutex> lock2(m2);  // 可能导致死锁
}

void thread2() {
    std::lock_guard<std::mutex> lock1(m2);
    std::lock_guard<std::mutex> lock2(m1);  // 可能导致死锁
}

解决方法:使用std::lock()一次性锁定多个锁。


第五幕:总结与展望

今天我们学习了std::threadstd::mutex的基本用法。以下是关键点的表格总结:

概念 描述
std::thread 用于创建和管理线程,支持join()detach()两种生命周期管理方式。
std::mutex 用于保护共享资源,避免数据竞争问题。
std::lock_guard RAII风格的锁管理工具,自动加锁和解锁,减少错误风险。

未来我们可以进一步探讨C++并发编程的高级话题,比如条件变量、原子操作、线程池等。希望今天的讲座对你有所帮助!如果你有任何问题,欢迎随时提问。谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注