C++多线程编程 3.互斥量、互斥锁
目录
1. 线程安全与互斥锁(std::mutex)
2. 互斥量死锁
3. std::lock_guard
4. std::unique_lock
(1)示例
(2)详细知识点
5. std::this_thread
(1)sleep_for
(2)sleep_until
(3)yield
(4)get_id
直接通过示例讲解:
1. 线程安全与互斥锁(std::mutex
)
int a = 0;
std::mutex mtx;
void func1()
{
for (int i = 0; i < 1000; i++)
{
mtx.lock(); // 加锁
a += 1;
mtx.unlock(); // 解锁
}
}
线程安全问题:在多线程环境中,如果多个线程同时访问和修改同一个共享变量(如这里的 a
),可能会导致数据竞争,产生不可预期的结果。
如果多个线程同时访问同一个变量,并且其中至少有一个线程对该变量进行了写操作,那么就会出现数据竞争问题。
数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果。
为了避免数据竞争问题,需要使用同步机制来确保多个线程之间对共享数据的访问是安全的。常见的同步机制包括互斥量、条件变量、原子操作等。
std::mutex
:互斥锁是一种同步原语,用于保护共享资源。
mtx.lock()
会尝试锁定互斥锁,如果锁已经被其他线程持有,当前线程会被阻塞,直到锁被释放。
mtx.unlock()
用于释放锁,允许其他线程获取该锁。
使用方式:在修改共享变量 a
之前调用 mtx.lock()
加锁,修改完成后调用 mtx.unlock()
解锁,确保同一时间只有一个线程可以修改 a
,从而保证线程安全。
2. 互斥量死锁
mutex m1, m2;
void func2()
{
for (int i = 0; i < 50; i++)
{
m1.lock();
m2.lock();
m1.unlock();
m2.unlock();
}
}
void func3()
{
for (int i = 0; i < 50; i++)
{
m2.lock();
m1.lock();
m2.unlock();
m1.unlock();
}
}
死锁原理:死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象。在 func2
中,线程先锁定 m1
再锁定 m2
;而在 func3
中,线程先锁定 m2
再锁定 m1
。如果两个线程同时执行,可能会出现 func2
持有 m1
等待 m2
,而 func3
持有 m2
等待 m1
的情况,从而导致死锁。
解决方法:为了避免死锁,所有线程应该按照相同的顺序获取锁,例如都先获取 m1
再获取 m2
。
3. std::lock_guard
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
std::mutex mtx3;
int b = 0;
void fun5()
{
for (int i = 0; i < 10000; i++)
{
std::lock_guard<std::mutex> lg(mtx3);
b++;
}
}
int main()
{
const int numThreads = 5; // 定义线程数量
std::vector<std::thread> threads;
// 创建并启动线程
for (int i = 0; i < numThreads; i++)
{
threads.emplace_back(fun5);
}
// 等待所有线程执行完毕
for (auto& thread : threads)
{
thread.join();
}
// 输出最终结果
std::cout << "Final value of b: " << b << std::endl;
std::cout << "Expected value of b: " << 10000 * numThreads << std::endl;
return 0;
}
作用:std::lock_guard
是一个 RAII(资源获取即初始化)风格的类模板,用于自动管理互斥锁的加锁和解锁操作。
工作原理:当创建 std::lock_guard
对象时,它会在构造函数中自动调用互斥锁的 lock()
方法加锁;当 std::lock_guard
对象离开其作用域时,它会在析构函数中自动调用互斥锁的 unlock()
方法解锁。这样可以避免手动调用 lock()
和 unlock()
可能导致的忘记解锁问题,提高代码的安全性。
4. std::unique_lock
(1)示例
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <vector>
int c = 0;
std::timed_mutex mtx6;
void func6() {
for (int i = 0; i < 5000; i++) {
std::unique_lock<std::timed_mutex> lg(mtx6, std::defer_lock);
std::this_thread::sleep_for(std::chrono::seconds(5));
if (lg.try_lock_for(std::chrono::seconds(5))) {
c++;
lg.unlock();
}
}
}
int main() {
const int numThreads = 3;
std::vector<std::thread> threads;
// 创建并启动线程
for (int i = 0; i < numThreads; i++) {
threads.emplace_back(func6);
}
// 等待所有线程执行完毕
for (auto& thread : threads) {
thread.join();
}
// 输出最终结果
std::cout << "Final value of c: " << c << std::endl;
std::cout << "Expected value of c: " << 5000 * numThreads << std::endl;
return 0;
}
特点:std::unique_lock
也是一个用于管理互斥锁的 RAII 类,但它比 std::lock_guard
更加灵活,包括延迟加锁、条件变量、超时等。
std::unique_lock<std::timed_mutex> lg(mtx6, std::defer_lock);
创建一个
std::unique_lock
对象lg
,用于管理mtx6
互斥锁。参数
defer_lock
:在创建std::unique_lock
对象时,传递defer_lock
参数表示不自动加锁,需要手动调用lock()
或try_lock()
等方法来加锁。
std::this_thread::sleep_for(std::chrono::seconds(5));
当前线程暂停执行 5 秒钟。
lg.try_lock_for(std::chrono::seconds(5));
try_lock_for
:try_lock_for
是std::unique_lock
提供的一个方法,用于尝试在指定的时间内锁定互斥锁。代码中的含义是:尝试在 5 秒内锁定互斥锁。如果在 5 秒内成功锁定,则返回true
,否则返回false
。
(2)详细知识点
1.灵活的锁定策略
可以在创建 std::unique_lock
对象时选择是否立即锁定互斥锁。例如:
std::mutex mtx;
std::unique_lock<std::mutex> lock1(mtx); // 立即锁定互斥锁
std::unique_lock<std::mutex> lock2(mtx, std::defer_lock); // 不立即锁定互斥锁
2. 支持锁的转移
可以将一个 std::unique_lock
对象的锁所有权转移给另一个 std::unique_lock
对象。例如:
std::mutex mtx;
std::unique_lock<std::mutex> lock1(mtx);
std::unique_lock<std::mutex> lock2(std::move(lock1)); // 转移锁所有权
3. 支持带超时的锁定操作
如果使用的是 std::timed_mutex
或 std::recursive_timed_mutex
,可以使用 try_lock_for
和 try_lock_until
方法进行带超时的锁定操作。例如:
std::timed_mutex mtx;
std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock);
if (lock.try_lock_for(std::chrono::seconds(2))) {
// 成功锁定互斥锁
} else {
// 锁定超时
}
5. std::this_thread
std::this_thread
是 C++ 标准库中的一个命名空间,提供了与当前线程相关的一些实用函数。
(1)sleep_for
使当前线程暂停执行指定的时间段。例如:
std::this_thread::sleep_for(std::chrono::seconds(2)); // 线程暂停 2 秒
(2)sleep_until
使当前线程暂停执行直到指定的时间点。例如:
auto wake_time = std::chrono::steady_clock::now() + std::chrono::seconds(3);
std::this_thread::sleep_until(wake_time); // 线程暂停到指定时间点
(3)yield
当前线程放弃执行权,允许其他线程执行。例如:
std::this_thread::yield(); // 当前线程让出 CPU 时间片
(4)get_id
返回当前线程的唯一标识符。例如:
std::thread::id this_id = std::this_thread::get_id();
std::cout << "Current thread ID: " << this_id << std::endl;