原子操作
C++中提供了原子操作(Atomic Operations)来确保多个线程之间对共享变量的原子性访问。原子操作是不可中断的操作,要么完全执行,要么完全不执行,不存在部分执行的情况。这样可以避免多线程并发操作导致的数据竞争和不确定行为。
C++中原子操作的相关类和函数定义在 <atomic>
头文件中。以下是常用的原子操作类和函数:
std::atomic<T>
:这是一个模板类,封装了一个可原子访问的对象。支持各种原子操作,如加载、存储、交换、比较和交换等。std::atomic_flag
:这是一个特殊的原子类型,用于实现简单的互斥锁(自旋锁)。std::atomic_flag
只支持两种操作:test_and_set()
和clear()
。- Atomic Load/Store(原子加载和存储):
std::atomic<T>::load()
和std::atomic<T>::store()
函数用于原子地加载和存储值。 - Atomic Exchange(原子交换):
std::atomic<T>::exchange()
函数用于原子地交换对象的值,并返回原来的值。 - Atomic Compare-and-Swap(原子比较和交换):
std::atomic<T>::compare_exchange_weak()
和std::atomic<T>::compare_exchange_strong()
函数用于原子地比较对象的值并进行交换。 - Atomic Fetch-and-Add(原子的加法操作):
std::atomic<T>::fetch_add()
和std::atomic<T>::fetch_sub()
函数用于原子地执行加法和减法操作,并返回之前的值。 - Atomic Flag Test-and-Set(原子标志测试与设置):
std::atomic_flag::test_and_set()
函数用于原子地设置标志,并返回之前的值。
这些原子操作提供了对共享变量的原子性访问,可以用于实现线程安全的并发操作。使用原子操作可以避免竞争条件和数据竞争等多线程并发问题,确保数据的一致性和可预测性。
以下是一个简单的示例,展示了原子操作的使用:
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> counter(0);
void incrementCounter() {
for (int i = 0; i < 100000; ++i) {
counter.fetch_add(1);
}
}
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
在上述示例中,我们使用 std::atomic<int>
类型的 counter
变量来实现原子的增加操作。两个线程分别调用 incrementCounter
函数,其中使用了原子的加法操作 fetch_add
来增加 counter
的值。通过使用原子操作,我们可以确保 counter
变量在多线程环境中的原子性访问。
注意:原子操作不仅仅适用于多线程环境,它们也可以在单线程环境中用于提供更高级别的同步和原子性保证。
mutex
C++中的 std::mutex
(互斥锁)是用于实现多线程同步的一种机制。它提供了一种确保在任何时刻只有一个线程可以访问共享资源的方式,以防止数据竞争和不一致性。以下是关于如何使用 std::mutex
的详细讲解:
-
包含头文件:
在使用std::mutex
之前,首先需要包含头文件<mutex>
。#include <mutex>
-
创建互斥锁对象:
使用std::mutex
类创建一个互斥锁对象。通常,你可以将其声明为全局变量或类成员变量,以确保多个线程都可以访问。std::mutex myMutex;
-
锁定互斥锁:
在访问共享资源之前,需要锁定互斥锁。使用std::unique_lock
或std::lock_guard
可以方便地实现这一点。-
使用
lock()
myMutex.lock();
-
使用
std::unique_lock
:std::unique_lock<std::mutex> lock(myMutex); // 此时已经锁定互斥锁,可以安全地访问共享资源 // ... // 在作用域结束时,unique_lock会自动释放互斥锁
-
使用
std::lock_guard
:std::lock_guard<std::mutex> lock(myMutex); // 在作用域内,互斥锁一直被锁定,作用域结束时自动释放 // ...
-
-
解锁互斥锁:
在完成对共享资源的操作后,需要解锁互斥锁以允许其他线程访问。解锁的方式取决于你使用的是std::unique_lock
还是std::lock_guard
。-
使用
unlock()
myMutex.unlock();
-
使用
std::unique_lock
:std::unique_lock<std::mutex> lock(myMutex); // 操作共享资源 lock.unlock(); // 手动解锁 // ...
-
使用
std::lock_guard
:std::lock_guard<std::mutex> lock(myMutex); // 操作共享资源 // lock_guard 在作用域结束时自动释放互斥锁 // ...
-
-
注意事项:
- 尽量避免在锁定期间执行长时间运行的操作,以减少其他线程等待的时间。
- 使用 RAII(资源获取即初始化)原则,确保在作用域结束时互斥锁自动释放。
condition_variable
条件变量是一种线程同步机制,用于在线程之间传递信息和实现线程的等待和唤醒。它通常与互斥锁一起使用,以确保在检查条件和等待条件期间不会发生竞争条件。以下是有关条件变量的详细讲解:
-
包含头文件:
在使用条件变量之前,需要包含头文件<condition_variable>
。#include <condition_variable>
-
创建条件变量和互斥锁对象:
使用std::condition_variable
类创建一个条件变量对象,以及一个std::mutex
互斥锁对象,用于保护与条件相关的共享数据。std::condition_variable myConditionVariable; std::mutex myMutex;
-
等待条件变为真:
在某个线程中,通过std::unique_lock
锁定互斥锁,并使用wait()
函数等待条件为真。wait()
函数在等待期间会释放互斥锁,允许其他线程修改共享数据。std::unique_lock<std::mutex> lock(myMutex); myConditionVariable.wait(lock, [] { return /* 条件为真的表达式 */; }); // 在此等待,直到条件为真
-
通知条件变为真:
在满足条件的情况下,通过notify_one()
或notify_all()
函数通知等待的线程条件已经为真。notify_one()
通知一个等待的线程。notify_all()
通知所有等待的线程。
myConditionVariable.notify_one(); // 或 myConditionVariable.notify_all();
-
等待时设置超时:
wait_for
和wait_until
函数允许在等待条件时设置超时,以便在超时时自动唤醒线程。auto timeout = std::chrono::milliseconds(500); if (myConditionVariable.wait_for(lock, timeout, [] { return /* 条件为真的表达式 */; })) { // 条件在超时前已经为真 } else { // 超时 }
-
注意事项:
- 使用条件变量时,通常与互斥锁一起使用,以确保在检查条件和等待条件期间不会发生竞争条件。
- 通知条件变为真的时机应在修改与条件相关的共享数据后,以避免竞争条件。
示例代码:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::condition_variable myConditionVariable;
std::mutex myMutex;
bool condition = false;
void waitForCondition()
{
std::unique_lock<std::mutex> lock(myMutex);
myConditionVariable.wait(lock, []
{ return condition; }); // 等待条件为真
std::cout << "Condition met, continuing..." << std::endl;
}
void notifyCondition()
{
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟一些操作
{
std::lock_guard<std::mutex> lock(myMutex);
condition = true; // 修改条件为真
std::cout << "notify..." << std::endl;
}
myConditionVariable.notify_one(); // 通知一个等待的线程
}
int main()
{
std::thread t1(waitForCondition);
std::thread t2(notifyCondition);
t1.join();
t2.join();
return 0;
}
在上述示例中,waitForCondition
线程等待条件变为真,而 notifyCondition
线程在一定时间后通知条件为真。条件变为真后,等待的线程被唤醒。