一:概述
原子等待是 C++20 引入的一个特性,主要用于多线程编程中。它允许线程高效地等待某个原子变量的值变化,而不是使用忙等待(busy waiting),这样可以减少 CPU 资源的浪费,提高程序的性能。
原子等待的引入主要是为了提高性能和资源利用率,尤其是在以下场景中:
-
减少上下文切换:传统的等待机制(如互斥锁、条件变量)常常涉及线程的阻塞与唤醒,这会导致上下文切换的开销。原子等待则允许线程在等待时进入休眠状态,避免了这些开销。
-
提高效率:在多线程环境中,频繁的状态变化可能会导致大量的线程等待和唤醒,使用原子等待可以更高效地管理这些状态变化,降低 CPU 的负担。
-
简化实现:原子等待提供了一种简单的机制,使得开发者可以更方便地实现高效的无锁数据结构和算法。
-
适用于高竞争场景:在高竞争的场景中,传统的锁机制可能会导致严重的性能下降,而原子等待能够减少锁竞争带来的问题。
二:多线程编程中还有哪些等待机制:
在多线程编程中,除了原子等待之外,还有几种常见的等待机制,每种机制都有其特定的使用场景和优缺点:
1. 忙等待(Busy Waiting)
-
描述:线程循环检查某个条件是否满足,一直占用 CPU。
-
优点:实现简单,延迟较低。
-
缺点:浪费 CPU 资源,导致性能下降,尤其在高负载时。
2. 互斥锁(Mutex)
-
描述:使用互斥锁控制对共享资源的访问,线程在获取不到锁时会阻塞。
-
优点:适用于复杂的临界区,能有效防止数据竞争。
-
缺点:锁的开销较大,可能导致死锁、优先级反转等问题。
3. 条件变量(Condition Variable)
-
描述:与互斥锁配合使用,线程可以在条件变量上等待,当条件满足时被通知。
-
优点:避免了忙等待,可以在条件满足时高效唤醒线程。
-
缺点:相对复杂,需要正确管理互斥锁和条件变量。
4. 信号量(Semaphore)
-
描述:允许多个线程访问共享资源的计数器。线程在访问资源时可以增加或减少信号量。
-
优点:可以控制同时访问共享资源的线程数量。
-
缺点:实现复杂,容易导致资源竞争和死锁。
三:原子等待的例子:
#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>
std::atomic<int> atomicValue(0);
void waitingThread() {
std::cout << "线程正在等待原子变量的值改变...\n";
int expected = 0;
std::atomic_wait(&atomicValue, expected);
std::cout << "线程恢复执行!原子变量的值变为: " << atomicValue.load() << "\n";
}
void notifyingThread() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作
atomicValue.store(1);
std::atomic_notify_one(&atomicValue);
std::cout << "已通知等待的线程。\n";
}
int main() {
std::thread t1(waitingThread);
std::thread t2(notifyingThread);
t1.join();
t2.join();
return 0;
}
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<bool> atomicBool{};
void doTheWork(){
std::cout << "Processing shared data." << '\n';
}
void waitingForWork(){
std::cout << "Worker: Waiting for work." << '\n';
atomicBool.wait(false);
doTheWork();
std::cout << "Work done." << '\n';
}
void setDataReady(){
atomicBool.store(true);
std::cout << "Sender: Data is ready." << '\n';
atomicBool.notify_one();
}
int main(){
std::cout << '\n';
std::thread t1(waitingForWork);
std::thread t2(setDataReady);
t1.join();
t2.join();
std::cout << '\n';
}