C++ mutex 锁的使用
Mutex介绍
在 C++ 中,
std::mutex
是标准库提供的用于线程同步的互斥锁(mutex),用于保护共享资源,防止多个线程同时访问造成数据竞争(data race)。下面将详细介绍std::mutex
的使用方法,包括基本用法、RAII 风格的锁管理(如std::lock_guard
和std::unique_lock
)等。
一、基本用法:直接使用 std::mutex
示例代码:
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 定义一个全局互斥锁
int shared_data = 0; // 共享数据void increment() {for (int i = 0; i < 100000; ++i) {mtx.lock(); // 加锁++shared_data; // 访问共享数据mtx.unlock(); // 解锁}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final shared_data: " << shared_data << std::endl;return 0;
}
说明:
mtx.lock()
:加锁,如果锁已被其他线程持有,则当前线程会阻塞,直到锁被释放。mtx.unlock()
:解锁,释放锁,允许其他线程获取该锁。- 注意:必须确保每次
lock()
后都有对应的unlock()
,否则可能导致死锁或其他线程永远无法获取锁。
二、使用 RAII 风格的锁管理:std::lock_guard
为了避免忘记解锁导致的潜在问题,推荐使用 RAII(Resource Acquisition Is Initialization)风格的锁管理工具,如 std::lock_guard
。它在构造时自动加锁,在析构时自动解锁,即使发生异常也能保证锁被释放。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 构造时加锁,析构时解锁++shared_data;}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final shared_data: " << shared_data << std::endl;return 0;
}
优点:
- 自动管理锁的生命周期:无需手动调用
lock()
和unlock()
,减少出错的可能。 - 异常安全:即使函数中抛出异常,
lock_guard
的析构函数也会被调用,确保锁被释放。
三、更灵活的锁管理:std::unique_lock
std::unique_lock
比 std::lock_guard
更加灵活,支持延迟加锁、手动解锁、锁的所有权转移等高级功能。但相应地,它的开销也略大于 std::lock_guard
。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void increment() {for (int i = 0; i < 100000; ++i) {std::unique_lock<std::mutex> lock(mtx); // 构造时加锁++shared_data;lock.unlock(); // 手动解锁(可选)// 这里可以进行一些不需要锁保护的操作lock.lock(); // 再次加锁(可选)// 继续访问共享数据}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final shared_data: " << shared_data << std::endl;return 0;
}
说明:
std::unique_lock
提供了更多的控制,比如可以在需要时手动加锁和解锁。- 适用于需要更细粒度控制锁的场景,但一般情况下
std::lock_guard
已经足够。
四、多个互斥锁的管理:std::lock
当需要同时锁定多个互斥锁时,为了避免死锁,推荐使用 std::lock
函数,它可以一次性锁定多个互斥锁,并且内部实现了死锁避免算法。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1;
std::mutex mtx2;
int data1 = 0;
int data2 = 0;void process_data() {std::lock(mtx1, mtx2); // 同时锁定两个互斥锁,避免死锁// 使用 std::lock_guard 的适配器 std::adopt_lock,表示互斥锁已经被锁定std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);// 访问共享数据++data1;++data2;
}int main() {std::thread t1(process_data);std::thread t2(process_data);t1.join();t2.join();std::cout << "Final data1: " << data1 << ", data2: " << data2 << std::endl;return 0;
}
说明:
std::lock(mtx1, mtx2)
:同时锁定mtx1
和mtx2
,内部使用死锁避免算法,确保不会发生死锁。std::adopt_lock
:告诉std::lock_guard
互斥锁已经被锁定,不需要再次加锁,lock_guard
只负责解锁。
五、其他相关工具
1. std::try_lock
std::try_lock
尝试锁定一个或多个互斥锁,如果无法立即锁定,则不会阻塞,而是返回失败状态。适用于需要非阻塞锁的场景。
2. std::recursive_mutex
如果同一个线程需要多次获取同一个互斥锁,可以使用 std::recursive_mutex
,它允许同一线程多次加锁而不会导致死锁。但需谨慎使用,避免逻辑错误。
六、总结
在 C++ 中使用 std::mutex
进行线程同步的基本步骤如下:
- 定义互斥锁:通常定义为全局变量或在需要保护的类中作为成员变量。
- 加锁和解锁:
- 直接使用
mtx.lock()
和mtx.unlock()
(不推荐,容易出错)。 - 使用
std::lock_guard
(推荐,简单安全)。 - 使用
std::unique_lock
(适用于需要更灵活控制的场景)。
- 直接使用
- 锁定多个互斥锁:使用
std::lock
避免死锁。 - 避免死锁:确保锁的获取顺序一致,或使用
std::lock
等工具。
通过合理使用 std::mutex
及其相关的 RAII 工具,可以有效地实现线程间的同步,保护共享资源,避免数据竞争和死锁问题。
七.案例
mutex.h
#ifndef _PROJECT_DEMO0525_MUTEX_TEST_H
#define _PROJECT_DEMO0525_MUTEX_TEST_H#include <thread>
#include <mutex>
#include <iostream>using namespace std;extern mutex mtx; // 互斥量extern int sharedResource; // 共享资源void testMutex();void getTestResult();#endif // _PROJECT_DEMO0525_MUTEX_TEST_H
mutex.cpp
#include "mutex_test.h"mutex mtx; // 互斥量int sharedResource = 0; // 共享资源const int sum = 1000000;void testMutex()
{for (int i = 0; i < sum; i++){mtx.lock(); // 手动锁定互斥量++sharedResource; // 访问共享资源mtx.unlock(); // 手动解锁互斥量}}void getTestResult()
{cout << "Final value of sharedResource :" << sharedResource << endl;
}
main.cpp
#include "mutex_test.h"void threadFunction()
{testMutex();
}int main()
{const int numThreads = 100; // 线程数thread threads[numThreads]; // 定义个线程数组for (int i = 0; i < numThreads; i++)
{threads[i] = thread(threadFunction);
}for (int i = 0; i < numThreads; i++)
{if(threads[i].joinable())threads[i].join();}cout << "All threads have finished ... " << endl;getTestResult();}
最后输出结果如下:
All threads have finished ...
Final value of sharedResource :100000000