1.概要
1.要点
2.相关链接
c++,一个线程,对外提供一个槽函数,当这个槽函数被调用的时候,线程休眠一段时间-优快云博客
qt 我想让一个udp线程休眠一段时间,休眠后只处理最新的数据,休眠期间的缓存数据我不要了-优快云博客 qt 一个线程让另一个线程休眠,可以吗-优快云博客
使用 Qt 的 moveToThread 方法创建的线程,如何让线程休眠-优快云博客
如何让一个使用 Qt 的 moveToThread 方法创建的udp线程休眠,且休眠期间的缓存数据自动丢弃-优快云博客
qt,一个线程,对外提供一个槽函数,当这个槽函数被调用的时候,线程休眠一段时间-优快云博客
2.内容
在 Qt 中,直接让一个线程控制另一个线程进入休眠状态并不是一个推荐的做法,因为这会涉及到线程间的直接操控,可能会导致竞争条件、死锁或其他不可预测的行为。Qt 的多线程编程模型鼓励通过信号和槽机制进行线程间的通信,而不是直接操控线程。
不过,如果你确实需要在某种条件下让一个线程暂停执行,可以考虑使用条件变量(QWaitCondition
)或互斥锁(QMutex
)等同步机制来实现。这些机制可以在一定程度上让一个线程等待另一个线程的信号。
下面是一个简单的例子,展示了如何使用 QWaitCondition
和 QMutex
来让一个线程等待另一个线程的信号:
#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QDebug>
QMutex mutex;
QWaitCondition condition;
class WorkerThread : public QThread
{
Q_OBJECT
public:
void run() override {
qDebug() << "Worker thread started, waiting for condition...";
mutex.lock();
condition.wait(&mutex);
mutex.unlock();
qDebug() << "Worker thread resumed!";
// Do some work...
}
};
class ControllerThread : public QThread
{
Q_OBJECT
public:
void run() override {
// Simulate some work in the controller thread
QThread::sleep(3);
qDebug() << "Controller thread signaling worker thread to resume...";
mutex.lock();
condition.wakeAll();
mutex.unlock();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
WorkerThread workerThread;
ControllerThread controllerThread;
workerThread.start();
controllerThread.start();
workerThread.wait();
controllerThread.wait();
return a.exec();
}
#include "main.moc"
在这个例子中:
WorkerThread
会在启动时等待一个条件变量condition
。ControllerThread
在模拟一些工作(通过QThread::sleep(3)
)后,会触发条件变量,让WorkerThread
继续执行。
这种设计保证了线程之间的同步,并且避免了直接操控线程的不安全性。
记住,在多线程编程中,正确的同步和通信机制是至关重要的,以避免竞态条件和确保程序的稳定性。如果你发现自己需要频繁地暂停和恢复线程,可能需要重新考虑你的设计,看是否有更合适的方式来组织线程间的工作。
3.关联知识
1.mutex.lock();
mutex.lock();
这行代码的含义是在多线程编程中锁定一个互斥量(mutex)。在多线程环境下,为了确保多个线程不会同时访问共享资源(比如全局变量、数据结构等),造成数据不一致或者竞争条件(race condition),我们需要使用互斥量来保护这些共享资源。
当你调用 mutex.lock();
时,当前线程会尝试获取这个互斥量的锁。如果锁已经被其他线程持有,那么当前线程会被阻塞(block),直到锁被释放(即其他线程调用了 mutex.unlock();
)并且当前线程成功获取到锁为止。
互斥量的使用遵循以下几个基本原则:
- 锁定前检查:在访问共享资源之前,必须确保已经成功获取了互斥量的锁。
- 尽早释放:在完成对共享资源的访问之后,应该尽快释放互斥量的锁,以便其他线程可以访问这些资源。
- 避免死锁:设计程序时要确保不会因为多个线程相互等待对方释放锁而导致死锁(deadlock)。
- 使用RAII(资源获取即初始化):在C++中,推荐使用RAII的方式来管理互斥量,比如使用
std::lock_guard
或std::unique_lock
,这样可以确保在异常发生时互斥量的锁也能被正确释放。
使用互斥量是多线程编程中同步线程、保护共享资源的一种常见且有效的方式。
2.mutex.lock(); condition.wait(&mutex);
在您提供的代码片段中,mutex.lock();
和 condition.wait(&mutex);
是多线程编程中常用的同步机制,通常与条件变量(std::condition_variable
)和互斥量(std::mutex
)一起使用。
-
mutex.lock();
:
这行代码的作用是锁定互斥量mutex
。在多线程环境中,为了保护共享资源或确保代码块的原子性执行,我们需要在访问这些资源或执行这些代码块之前锁定互斥量。如果互斥量已经被其他线程锁定,则当前线程会阻塞在这里,直到互斥量被释放并成功锁定为止。 -
condition.wait(&mutex);
:
这行代码的作用是使当前线程等待一个条件变量condition
被通知(notify)或超时。这里有几个关键点需要注意:&mutex
:这个参数是指向当前已经锁定的互斥量的指针。在调用wait
方法时,它会自动释放互斥量的锁,并使当前线程阻塞,直到条件变量被通知或超时。当线程被唤醒或超时后,它会自动重新锁定互斥量。- 阻塞与唤醒:当前线程在调用
wait
后会阻塞,直到另一个线程调用condition.notify_one()
或condition.notify_all()
来唤醒等待的线程。这些通知函数通常会在某个条件满足时被调用,以通知等待的线程可以继续执行。 - 超时(可选):
wait
方法还可以接受一个超时参数(例如std::chrono::milliseconds
),用于指定等待的最大时间。如果在这段时间内条件变量没有被通知,线程会被唤醒并继续执行。
结合使用互斥量和条件变量,可以实现复杂的线程同步逻辑,比如生产者-消费者模型、线程池等。在这种模式下,生产者线程会在满足某个条件时通知消费者线程,而消费者线程则会在条件不满足时等待通知。通过互斥量来确保对共享资源的访问是线程安全的,通过条件变量来实现线程间的同步和通信。
3..condition.wait(&mutex);
condition.wait(&mutex);
是 C++ 标准库中条件变量(std::condition_variable
)的一个成员函数,用于在多线程编程中实现线程间的同步。这个函数通常与互斥量(std::mutex
)一起使用,以确保线程在等待条件满足时能够安全地阻塞,并在条件满足时被唤醒。
下面是 condition.wait(&mutex);
调用的一些关键点:
-
互斥量锁定:在调用
wait
之前,调用线程必须已经锁定了与条件变量相关联的互斥量(即mutex
)。这是通过mutex.lock();
实现的。这个互斥量用于保护共享资源或条件,确保在检查条件或修改条件时不会发生数据竞争。 -
释放锁并等待:当调用
condition.wait(&mutex);
时,会发生以下几件事情:- 当前线程会自动释放
mutex
的锁,并阻塞,等待条件变量的通知。 - 当另一个线程调用
condition.notify_one();
或condition.notify_all();
时(通常是在某个条件满足之后),等待的线程可能会被唤醒。 - 被唤醒的线程在继续执行之前会自动重新锁定
mutex
。
- 当前线程会自动释放
-
唤醒条件:线程被唤醒的条件有两种:
- 另一个线程调用了
condition.notify_one();
,并且当前线程是等待该条件变量的线程之一(具体哪个线程被唤醒是不确定的,由操作系统调度)。 - 另一个线程调用了
condition.notify_all();
,这会唤醒所有等待该条件变量的线程。 - 另外,
wait
函数还可以接受一个超时参数(如std::chrono::milliseconds
),如果指定了超时,并且超时时间已到而条件还未满足,线程也会被唤醒。
- 另一个线程调用了
-
使用场景:
condition.wait(&mutex);
通常用于实现生产者-消费者模型、线程间的信号量机制或其他需要线程同步等待某个条件的场景。 -
注意事项:
- 在调用
wait
之前,必须确保已经锁定了互斥量。 - 在等待条件满足时,互斥量会被释放,以允许其他线程修改条件或共享资源。
- 被唤醒的线程在继续执行之前会重新锁定互斥量,以确保对共享资源的访问是线程安全的。
- 应避免在持有互斥量锁的情况下调用可能会阻塞的操作(如
wait
),因为这可能会导致死锁或性能问题。正确的做法是先释放锁,再等待,然后在需要时重新获取锁。
- 在调用
4.condition.wait(&mutex);
condition.wait(&mutex);
是 C++ 标准库中条件变量(std::condition_variable
)的一个重要成员函数,它用于实现线程间的同步等待。这个函数的设计目的是让当前线程在某个条件不满足时能够安全地阻塞,直到另一个线程通知它条件已经满足为止。
以下是关于 condition.wait(&mutex);
的详细解释:
调用前提
在调用 condition.wait(&mutex);
之前,当前线程必须已经锁定了与条件变量 condition
相关联的互斥量 mutex
。这是通过调用 mutex.lock();
来实现的。锁定互斥量是确保在检查条件或等待条件时不会发生数据竞争的关键步骤。
函数行为
- 释放锁并阻塞:
- 当
condition.wait(&mutex);
被调用时,当前线程会自动释放mutex
的锁,并且进入阻塞状态,等待条件变量的通知。
- 当
- 等待通知:
- 当前线程会一直阻塞,直到另一个线程调用
condition.notify_one();
或condition.notify_all();
来通知它。这些通知函数通常会在某个特定条件满足时被调用。
- 当前线程会一直阻塞,直到另一个线程调用
- 重新锁定锁:
- 当条件变量被通知,并且当前线程被唤醒时,它会自动重新锁定
mutex
。这是为了确保在继续执行后续代码时,对共享资源的访问是线程安全的。
- 当条件变量被通知,并且当前线程被唤醒时,它会自动重新锁定
使用场景
condition.wait(&mutex);
通常用于以下场景:
-
生产者-消费者模型:在生产者线程生产数据,消费者线程消费数据的场景中,条件变量可以用于同步这两个线程。当没有数据可供消费时,消费者线程会等待在条件变量上;当生产者线程生产出数据后,会通知消费者线程。
-
线程间的信号量机制:条件变量可以实现类似信号量的机制,用于在线程之间传递信号或状态信息。
-
其他需要线程同步等待的场景:任何需要线程在某个条件满足之前等待,并在条件满足后被唤醒的场景都可以使用条件变量。
注意事项
-
避免死锁:在设计使用条件变量和互斥量的程序时,要特别注意避免死锁。例如,确保在调用
wait
之前已经锁定了互斥量,并且在被唤醒后能够正确地重新锁定互斥量。 -
使用正确的锁类型:
std::condition_variable
必须与std::unique_lock<std::mutex>
或std::lock_guard<std::mutex>
一起使用,而不是直接与std::mutex
的裸锁一起使用。虽然wait
函数接受的是std::unique_lock<std::mutex>
的指针或引用,但std::lock_guard
由于其自动解锁的特性,通常不适合与wait
一起使用。 -
处理虚假唤醒:虽然不常见,但条件变量有时可能会遇到虚假唤醒(spurious wakeup)的情况,即线程在没有被通知的情况下被唤醒。为了处理这种情况,通常会在
wait
调用中使用一个循环,并在循环中检查条件是否真的满足。
综上所述,condition.wait(&mutex);
是 C++ 多线程编程中实现线程同步等待的重要工具之一。正确地使用它可以帮助你设计出高效、安全的并发程序。
5.std::condition_variable
std::condition_variable
是 C++11 标准库中提供的一个用于线程同步的类。它允许线程在某些条件不满足时挂起(等待),并在条件满足时被其他线程唤醒。std::condition_variable
通常与 std::mutex
(互斥锁)一起使用,以确保线程安全。
基本特点
- 线程同步:
std::condition_variable
用于实现线程间的同步等待,允许线程等待某个条件变量,并在条件满足时被唤醒。 - 依赖互斥锁:
std::condition_variable
本身不提供任何互斥机制,它依赖于外部提供的互斥锁(如std::mutex
)来保护共享数据和同步线程之间的通信。 - 避免忙等待:通过使用条件变量,线程可以在等待条件满足时释放 CPU 资源,避免忙等待,从而提高程序的整体性能。
主要成员函数
- wait(std::unique_lockstd::mutex& lock):挂起当前线程,直到另一个线程调用
notify_one()
或notify_all()
。调用时需要提供一个互斥锁的引用,该锁将在等待期间被解锁,并在唤醒后自动重新锁定。 - wait(std::unique_lockstd::mutex& lock, std::function<bool()> pred):与上面的
wait
方法类似,但它接受一个条件判断函数pred
。线程将等待直到pred()
返回true
。 - notify_one():唤醒一个正在等待的线程(如果有)。
- notify_all():唤醒所有正在等待的线程。
使用步骤
- 定义互斥锁和条件变量:首先,定义一个互斥锁(如
std::mutex
)和一个std::condition_variable
条件变量。 - 锁定互斥锁:在调用
std::condition_variable
的wait
或wait_for
等函数之前,必须先锁定互斥锁。 - 调用条件变量的等待函数:调用
wait
、wait_for
或wait_until
等函数来等待条件成立。这些函数会释放互斥锁,使其他线程可以进入临界区,并在条件变量上等待。 - 检查条件:在
wait
、wait_for
或wait_until
返回后,通常需要重新检查条件是否满足,因为可能存在虚假唤醒(spurious wakeup)的情况。 - 解锁互斥锁:在离开临界区时,需要解锁互斥锁。
注意事项
- 避免死锁:在设计使用条件变量和互斥量的程序时,要特别注意避免死锁。
- 处理虚假唤醒:由于操作系统或 C++ 标准库的实现原因,条件变量可能会出现虚假唤醒的情况。因此,在等待函数返回后,通常需要重新检查条件是否满足。
- 确保线程安全:在使用条件变量时,必须确保在调用
wait
、notify_one
和notify_all
等函数时持有正确的互斥锁,以避免竞态条件(race condition)和其他线程安全问题。
示例代码
以下是一个简单的生产者-消费者模型示例,展示了 std::condition_variable
的使用:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex g_mtx;
std::condition_variable g_cv;
std::queue<int> g_queue;
bool g_done = false;
// 生产者函数
void producer(int id) {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lck(g_mtx);
g_queue.push(id * 10 + i);
std::cout << "Produced: " << id * 10 + i << std::endl;
lck.unlock();
g_cv.notify_one(); // 通知一个等待的线程
}
{
std::lock_guard<std::mutex> lck(g_mtx);
g_done = true;
}
g_cv.notify_all(); // 通知所有等待的线程
}
// 消费者函数
void consumer() {
std::unique_lock<std::mutex> lck(g_mtx);
while (!g_done && g_queue.empty()) {
g_cv.wait(lck); // 等待条件成立
}
while (!g_queue.empty()) {
int val = g_queue.front();
g_queue.pop();
std::cout << "Consumed: " << val << std::endl;
lck.unlock(); // 模拟耗时操作,等待100毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(100));
lck.lock();
}
}
int main() {
std::thread producers[2]; // 生产者线程池
std::thread consumers[5]; // 消费者线程池
// 创建生产者
for (int i = 0; i < 2; ++i) {
producers[i] = std::thread(producer, i);
}
// 创建消费者
for (int i = 0; i < 5; ++i) {
consumers[i] = std::thread(consumer);
}
// 等待生产者完成
for (auto& t : producers) {
t.join();
}
// 唤醒所有等待的消费者(尽管这里可能不是必需的,因为 done 标志已经设置)
g_cv.notify_all();
// 等待消费者完成
for (auto& t : consumers) {
t.join();
}
return 0;
}
在这个例子中,生产者和消费者线程使用条件变量 g_cv
和互斥锁 g_mtx
来同步对共享队列 g_queue
的访问。生产者生产数据后,通过调用 notify_one
来唤醒一个等待的消费者线程。当所有生产者都完成生产后,它们将 done
标志设置为 true
,并通过调用 notify_all
来唤醒所有的消费者线程。
6.condition.wait(&mutex);和 QThread::sleep(3);都需要线程停止一段时间,这两种停止方式有什么差别吗
condition.wait(&mutex);
和 QThread::sleep(3);
虽然都涉及到线程停止一段时间,但它们在机制、用途和实现方式上存在显著的差别。
机制与用途
方法 | 机制 | 用途 |
---|---|---|
condition.wait(&mutex); | 线程同步机制 | 线程在条件不满足时挂起,等待其他线程通过 notify_one 或 notify_all 唤醒。 |
QThread::sleep(3); | 线程延时机制 | 线程主动放弃时间片,休眠指定的时间(这里是3秒),然后继续执行。 |
实现方式
方法 | 实现方式 | 关键点 |
---|---|---|
condition.wait(&mutex); | 调用时需要传入一个互斥锁的引用。线程在等待期间会释放该互斥锁,进入阻塞状态。当条件变量被通知时,线程被唤醒并重新锁定互斥锁。 | - 依赖于互斥锁来保护共享资源。<br>- 线程在等待期间会释放锁,避免死锁。<br>- 需要处理虚假唤醒的情况。 |
QThread::sleep(3); | 线程调用 sleep 函数后,会主动放弃 CPU 时间片,休眠指定的时间(这里是3秒)。休眠期间,线程不会执行任何操作,但也不会释放它所持有的任何锁。 | - 线程休眠期间不执行任何操作。<br>- 休眠时间结束后,线程自动恢复执行。<br>- 休眠期间不会释放锁,可能导致死锁。 |
注意事项
方法 | 注意事项 |
---|---|
condition.wait(&mutex); | - 必须在锁定互斥锁的情况下调用。<br>- 唤醒后通常需要重新检查条件是否满足,因为可能存在虚假唤醒。<br>- 确保在调用 wait 、notify_one 和 notify_all 时持有正确的互斥锁,以避免竞态条件。 |
QThread::sleep(3); | - 休眠期间不会释放锁,可能导致死锁,特别是在多线程共享资源时。<br>- 休眠时间可能受到系统负载和线程优先级的影响,实际休眠时间可能比指定时间长。<br>- 休眠期间不会响应中断信号。 |
示例场景
condition.wait(&mutex);
适用于生产者-消费者模型、线程池等场景,其中线程需要等待某个条件成立(如队列不为空)才能继续执行。QThread::sleep(3);
适用于简单的延时操作,如定时任务、模拟长时间操作等场景,其中线程不需要等待其他线程的通知,只需暂停执行一段时间。
总结
condition.wait(&mutex);
和 QThread::sleep(3);
是两种不同用途的线程停止方式。前者用于线程间的同步等待,依赖于条件变量和互斥锁来实现;后者用于线程延时,线程主动放弃时间片进行休眠。在选择使用哪种方式时,需要根据具体的应用场景和需求来决定。
7.lck.unlock();
在C++多线程编程中,lck.unlock();
这条语句用于解锁一个之前被锁定的互斥锁(std::mutex
)。这里,lck
是一个 std::unique_lock<std::mutex>
或 std::lock_guard<std::mutex>
的实例,它管理着一个互斥锁的生命周期,包括锁定和解锁操作。
std::unique_lock
和 std::lock_guard
-
std::unique_lock<std::mutex>
:这是一个灵活的锁管理器,它允许你锁定和解锁互斥锁,还可以在需要时将锁的所有权转移给其他std::unique_lock
实例。它通常与条件变量(std::condition_variable
)一起使用,因为在等待条件变量时,需要解锁互斥锁以避免死锁。 -
std::lock_guard<std::mutex>
:这是一个更简单的锁管理器,它在构造时自动锁定互斥锁,并在析构时自动解锁。它不允许手动解锁,也不允许锁的所有权转移。它的主要优点是简洁和安全性,因为它确保了互斥锁总是会在作用域结束时被解锁。
lck.unlock();
的使用
当你使用 std::unique_lock<std::mutex>
时,你可能会在某些情况下需要手动解锁互斥锁。例如,在调用条件变量的 wait
方法之前,你需要解锁互斥锁,因为 wait
方法会在内部锁定互斥锁直到条件满足。在这种情况下,你可以使用 lck.unlock();
来解锁互斥锁。
std::mutex mtx;
std::condition_variable cv;
std::unique_lock<std::mutex> lck(mtx);
// ... 做一些事情,可能需要锁定互斥锁 ...
// 解锁互斥锁并等待条件变量
cv.wait(lck, []{ return some_condition; });
// 注意:当 wait 返回时,lck 已经重新锁定了互斥锁
// ... 继续做一些事情,此时互斥锁仍然被锁定 ...
// 手动解锁互斥锁(如果需要)
lck.unlock();
在上面的代码中,lck.unlock();
在 cv.wait(lck, ...);
之后被调用,这是因为在 wait
方法返回之前,它会重新锁定互斥锁。如果你之后不再需要锁定互斥锁,你可以调用 lck.unlock();
来手动解锁。
然而,如果你使用的是 std::lock_guard<std::mutex>
,则你不能调用 unlock
方法,因为 std::lock_guard
不允许手动解锁。它的设计目的是确保互斥锁在作用域结束时自动解锁,从而避免忘记解锁导致的潜在死锁问题。
注意事项
- 确保在调用
lck.unlock();
之前,你已经正确地锁定了互斥锁。 - 避免在多个线程中同时操作同一个
std::unique_lock
实例,除非你有明确的同步策略。 - 在解锁互斥锁之后,如果需要再次锁定它,你应该使用相应的锁定方法(如
lck.lock()
)来重新锁定。 - 谨慎使用手动解锁,因为它可能导致代码更难理解和维护。在大多数情况下,
std::lock_guard
或std::unique_lock
的自动管理功能已经足够满足需求。
8.std::condition_variable
std::condition_variable
是 C++11 标准库中提供的一个同步机制,用于在多个线程之间实现条件变量的等待/通知机制。它允许一个或多个线程在某个条件为真时继续执行。条件变量总是与互斥锁(std::mutex
)一起使用,以保护共享数据和避免竞态条件。
基本用法
-
定义条件变量:
std::condition_variable cv;
-
等待条件变量:
等待线程通常会调用wait
方法,该方法会原子性地解锁互斥锁并使线程阻塞,直到另一个线程通知(或虚假唤醒)为止。wait
方法通常需要一个谓词(predicate)作为参数,该谓词是一个返回bool
类型的函数或 lambda 表达式,用于指示等待的条件是否已经满足。std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return condition; }); // condition 是等待的条件
-
通知条件变量:
当某个线程改变了某个条件(例如,某个共享变量的值)并且希望其他等待该条件的线程继续执行时,它会调用notify_one
(通知一个等待线程)或notify_all
(通知所有等待线程)。{ std::lock_guard<std::mutex> lock(mtx); // 改变条件 condition = true; } cv.notify_one(); // 或者 cv.notify_all();
注意事项
- 避免死锁:确保在调用
wait
之前已经锁定了与条件变量相关联的互斥锁。 - 使用谓词:
wait
方法的谓词参数可以帮助避免虚假唤醒(spurious wakeups),即线程在没有被通知的情况下被唤醒。 - 通知时机:在改变条件并且确保所有等待该条件的线程能够看到这些改变之后,再调用
notify_one
或notify_all
。 - 效率:条件变量是高效的同步机制,特别是在等待时间较长或者线程数量较多的情况下,因为它们允许线程在等待条件满足时进入睡眠状态,而不是忙等待(busy-waiting)。
示例
以下是一个简单的生产者-消费者模型,使用 std::condition_variable
来同步生产者和消费者线程:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> dataQueue;
bool ready = false;
void producer() {
{
std::lock_guard<std::mutex> lock(mtx);
dataQueue.push(42);
ready = true;
}
cv.notify_one();
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
while (!dataQueue.empty()) {
int value = dataQueue.front();
dataQueue.pop();
std::cout << "Consumed: " << value << std::endl;
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
在这个例子中,生产者线程生产数据(将数字 42 放入队列),然后通过 notify_one
通知消费者线程数据已经准备好。消费者线程在等待条件变量时被阻塞,直到被通知并且条件满足(ready
为 true
),然后开始消费数据。
9.我应该如何使用 std::condition_variable 呢
使用 std::condition_variable
需要遵循一定的步骤和最佳实践来确保线程间的同步和正确的条件等待/通知机制。以下是一个详细的使用指南:
1. 定义条件变量和互斥锁
首先,你需要定义一个 std::condition_variable
对象和一个与之关联的 std::mutex
对象。这些通常作为类的成员变量或全局变量出现。
std::mutex mtx;
std::condition_variable cv;
2. 等待线程:使用 wait
方法
等待线程需要获取互斥锁,并调用 cv.wait()
方法。这个方法会原子性地解锁互斥锁并使线程阻塞,直到另一个线程调用 notify_one()
或 notify_all()
,或者发生虚假唤醒。
通常,wait
方法会带有一个谓词(一个返回 bool
的函数或 lambda 表达式),用于检查等待的条件是否已经满足。如果谓词返回 false
,线程会继续等待;如果返回 true
,线程会停止等待并继续执行。
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return condition; }); // condition 是等待的条件
在这里,condition
应该是一个与你的程序逻辑相关的布尔表达式,表示等待的条件是否已经满足。
3. 通知线程:使用 notify_one
或 notify_all
当某个线程改变了某个条件(例如,更新了共享数据),并且希望其他等待该条件的线程继续执行时,它会调用 cv.notify_one()
来通知一个等待线程,或者调用 cv.notify_all()
来通知所有等待线程。
{
std::lock_guard<std::mutex> lock(mtx);
// 改变条件
condition = true;
}
cv.notify_one(); // 或者 cv.notify_all();
注意,在改变条件和通知之前,你应该先锁定互斥锁,以确保条件的改变对等待线程是可见的,并且避免竞态条件。
4. 编写完整的代码示例
以下是一个完整的代码示例,展示了如何使用 std::condition_variable
来实现一个简单的生产者-消费者模型:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> dataQueue;
bool ready = false;
void producer(int count) {
for (int i = 0; i < count; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
dataQueue.push(i);
ready = true;
}
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !dataQueue.empty(); });
int value = dataQueue.front();
dataQueue.pop();
lock.unlock(); // 解锁以便生产者可以推入新数据
std::cout << "Consumed: " << value << std::endl;
if (value == /* 某个终止条件 */) {
break; // 退出循环,结束消费者线程
}
}
}
int main() {
std::thread producerThread(producer, 10); // 生产10个数据
std::thread consumerThread(consumer);
producerThread.join();
consumerThread.join();
return 0;
}
在这个例子中,生产者线程生产数据并将其推入队列,然后通知消费者线程数据已经准备好。消费者线程等待数据可用,然后消费数据。注意,消费者线程在消费数据后解锁互斥锁,以便生产者线程可以推入新数据。同时,消费者线程在检测到某个终止条件时退出循环,结束线程。
注意事项
- 确保在调用
wait
之前已经锁定了互斥锁。 - 使用谓词来避免虚假唤醒。
- 在改变条件和通知之前锁定互斥锁。
- 考虑使用
notify_all
而不是notify_one
,如果你希望唤醒所有等待线程(例如,在条件变量用于广播事件时)。 - 注意线程的生命周期和同步机制的正确销毁,以避免资源泄漏和未定义行为。