c++ 学习笔记之多线程:线程锁,条件变量,唤醒指定线程
基于CAS线程加锁方式
CAS(Compare-And-Swap)和 mutex
都是用于实现线程安全的技术,但它们适用于不同的场景,具有不同的性能和复杂性。下面是对两者的区别和使用场景的详细解释:
CAS(Compare-And-Swap)
工作原理
CAS 是一种无锁(lock-free)的同步机制,通过原子操作来比较和交换变量的值。它的核心思想是:只有当变量的当前值等于预期值时,才将其更新为新值,否则重新尝试。这个操作通常在硬件级别支持,以确保其原子性。
优点
- 高性能:由于没有锁的开销,CAS 通常比互斥锁更快,尤其是在争用较少的情况下。
- 无锁编程:CAS 允许多线程环境下的无锁编程,避免了死锁问题。
- 可扩展性:在高并发场景下,CAS 能提供更好的可扩展性,因为它减少了线程间的竞争。
缺点
- 复杂性:编写无锁代码更复杂,容易出错,特别是在处理复杂数据结构时。
- ABA问题:CAS 操作可能会受到 ABA 问题的影响,即一个值被修改成另一个值后又变回原值,导致 CAS 操作误认为值没有变化。
- 不适合长时间操作:如果需要进行长时间操作,CAS 可能不适用,因为在高争用情况下,CAS 可能会反复失败,导致性能下降。
使用场景
- 短小且频繁的操作:适用于操作简短且需要高频率执行的场景,如计数器、指针交换等。
- 高并发环境:适用于高并发但低争用的场景,如无锁队列、栈等数据结构。
mutex
(互斥锁)
工作原理
mutex
是一种基于锁的同步机制,用于保护临界区,确保同一时间只有一个线程可以访问共享资源。线程在进入临界区前必须获取锁,离开时释放锁。
优点
- 易于理解和使用:
mutex
的使用方式相对简单,易于理解和实现。 - 适用广泛:适用于需要保护临界区的各种场景,包括长时间的复杂操作。
- 解决复杂同步问题:对于复杂的共享资源访问,
mutex
提供了可靠的解决方案。
缺点
- 性能开销:获取和释放锁的操作有一定的性能开销,尤其是在高并发环境下。
- 死锁风险:如果使用不当,可能会导致死锁,即两个或多个线程互相等待对方释放锁。
- 上下文切换开销:在高争用情况下,线程可能频繁阻塞和唤醒,导致上下文切换开销。
使用场景
- 复杂操作和长时间操作:适用于需要长时间保护的临界区操作,特别是复杂的共享数据结构修改。
- 低到中等并发环境:适用于并发度不高的环境,或者即使在高并发环境中,也能有效管理临界区的访问。
- 简单同步需求:对于简单的同步需求,如单个资源的访问控制,
mutex
是一个可靠的选择。
总结
- CAS:适用于短小、频繁且简单的操作,特别是在高并发但低争用的场景中,能提供更好的性能和可扩展性,但编写无锁代码更复杂。
mutex
:适用于长时间、复杂的操作,以及需要可靠保护的临界区,易于理解和使用,但在高并发环境下性能可能不如无锁方案。
示例:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> num(0); // 使用std::atomic定义一个原子整数
void increment() {
for (int i = 0; i < 1000; ++i) {
int expected = num.load();
while (!num.compare_exchange_weak(expected, expected + 1)) {
// 如果CAS失败,expected会被更新为当前的值,继续尝试
}
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value of num: " << num << std::endl;
return 0;
}
条件变量:std::condition_variable
条件变量的工作原理
-
线程等待 (
wait
):- 当线程调用
wait
方法时,它会自动释放与条件变量相关联的互斥锁,并进入等待状态。 - 线程在等待期间会阻塞,直到被其他线程通知。
- 重要的是,
wait
方法会确保线程在被唤醒时会重新获得互斥锁,这样它可以安全地检查和更新条件。
- 当线程调用
-
线程通知 (
notify_all
或notify_one
):- 当某个线程改变了共享状态(即更新了条件)后,它会调用
notify_all
或notify_one
方法来通知其他线程。 notify_all
会唤醒所有在条件变量上等待的线程,而notify_one
只会唤醒一个线程(如果有多个线程等待)。
- 当某个线程改变了共享状态(即更新了条件)后,它会调用
来控制多个线程的执行顺序。以下是一个示例代码,实现了四个线程按照指定顺序(2 -> 1 -> 4 -> 3)循环执行:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <algorithm>
std::mutex mtx;
std::condition_variable cv;
int current_thread = 2; // 初始线程编号
const std::vector<int> order = {2, 1, 4, 3}; // 执行顺序
const int num_iterations = 10; // 每个线程执行的循环次数
void thread_function(int thread_id) {
for (int i = 0; i < num_iterations; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [thread_id]() { return current_thread == thread_id; });
// 执行线程的任务
std::cout << "Thread " << thread_id << " is executing\n";
// 更新当前线程编号
auto it = std::find(order.begin(), order.end(), thread_id);
if (it != order.end() && ++it != order.end()) {
current_thread = *it;
} else {
current_thread = order[0];
}
// 唤醒所有等待线程
cv.notify_all();
}
}
int main() {
std::vector<std::thread> threads;
// 创建四个线程
for (int i = 1; i <= 4; ++i) {
threads.emplace_back(thread_function, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
return 0;
}
notify_all
和 notify_one
的区别
-
notify_all
:- 功能:唤醒所有在条件变量上等待的线程。
- 使用场景:当条件变化时,你希望所有等待的线程都能被唤醒并检查条件。例如,当一个共享资源的状态发生变化,并且所有等待的线程都需要重新检查状态时,使用
notify_all
是合适的。 - 缺点:如果有大量线程等待,并且每次通知都会唤醒所有线程,可能会导致性能问题,因为所有线程都会被唤醒并重新竞争锁。
-
notify_one
:- 功能:唤醒一个在条件变量上等待的线程。如果有多个线程等待,哪个线程被唤醒是不确定的。
- 使用场景:当条件变化时,只需要唤醒一个线程进行处理,其他线程可以继续等待。例如,当有线程处理某个任务时,你可能只需要唤醒一个线程来处理它,并且其他线程可以继续等待。
- 优点:比
notify_all
更高效,特别是在只需唤醒一个线程的情况下,减少了不必要的线程唤醒和锁竞争。
多线程编程中,怎么唤醒指定的线程
在多线程编程中,标准的 C++ 条件变量 (std::condition_variable
) 并不提供直接指定唤醒哪个特定线程的功能。std::condition_variable
提供的 notify_one()
和 notify_all()
方法仅仅是唤醒一个或所有等待线程,并不支持精确指定具体的线程。要实现更细粒度的线程控制,你需要采用其他方法。以下是一些常见的方法来间接实现唤醒特定线程的需求:
1. 线程标识符与条件变量
你可以使用标识符或某种标志来控制哪个线程应该继续执行。这种方法不直接唤醒特定线程,但通过条件变量和额外的状态管理来实现间接的控制。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
std::mutex mtx;
std::condition_variable cv;
bool ready[4] = {false, false, false, false}; // 每个线程的准备状态
void worker(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [id]{ return ready[id]; }); // 等待特定条件
std::cout << "Worker " << id << " is processing\n";
}
void set_ready(int id) {
std::lock_guard<std::mutex> lock(mtx);
ready[id] = true;
cv.notify_all(); // 或 notify_one(),具体取决于你的需求
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(worker, i);
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间
// 唤醒特定的线程
set_ready(2); // 只唤醒 id 为 2 的线程
for (auto& t : threads) {
t.join();
}
return 0;
}
2. 使用 std::promise
和 std::future
std::promise
和 std::future
提供了另一种方式来管理线程间的同步,并可以用于将结果传递给特定线程。
#include <iostream>
#include <thread>
#include <future>
#include <vector>
std::vector<std::promise<void>> promises(4);
std::vector<std::future<void>> futures;
void worker(int id) {
futures[id].wait(); // 等待对应的 promise 被设置
std::cout << "Worker " << id << " is processing\n";
}
int main() {
for (int i = 0; i < 4; ++i) {
futures.push_back(promises[i].get_future());
std::thread(worker, i).detach();
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟准备时间
// 唤醒特定的线程
promises[2].set_value(); // 唤醒 id 为 2 的线程
std::this_thread::sleep_for(std::chrono::seconds(1)); // 给线程时间完成任务
return 0;
}
原文地址:https://blog.csdn.net/qq_62975986/article/details/140578141
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!