条件变量真的需要另一个变量吗?
Does a condition variable really need another variable?
注意:我将在 C++ 中给出示例,但我相信我的问题与语言无关。如果我错了请纠正我。
只是为了让您真正了解我 - 我想在这里学习的是 该工具的功能,除此之外别无其他。不是它通常用来做什么,也不是约定俗成,而是钝器的作用。在这种情况下 - 条件变量的作用。
到目前为止,在我看来这是一种允许线程等待(阻塞)直到其他线程向它们发出信号(解除阻塞)的简单机制。仅此而已,不处理关键部分访问或数据访问(当然它们可以用于此,但这只是程序员的选择问题)。此外,信号通常仅在发生重要事件(例如加载数据)时才完成,但理论上它可以随时调用。到目前为止正确吗?
现在,我看到的每个示例都使用条件变量对象(例如 std::condition_variable
),但也使用一些附加变量来标记是否发生了某些事情(例如 bool dataWasLoaded
)。从 https://thispointer.com//c11-multithreading-part-7-condition-variables-explained/:
看这个例子
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application
{
std::mutex m_mutex;
std::condition_variable m_condVar;
bool m_bDataLoaded;
public:
Application()
{
m_bDataLoaded = false;
}
void loadData()
{
// Make This Thread sleep for 1 Second
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Loading Data from XML" << std::endl;
// Lock The Data structure
std::lock_guard<std::mutex> guard(m_mutex);
// Set the flag to true, means data is loaded
m_bDataLoaded = true;
// Notify the condition variable
m_condVar.notify_one();
}
bool isDataLoaded()
{
return m_bDataLoaded;
}
void mainTask()
{
std::cout << "Do Some Handshaking" << std::endl;
// Acquire the lock
std::unique_lock<std::mutex> mlock(m_mutex);
// Start waiting for the Condition Variable to get signaled
// Wait() will internally release the lock and make the thread to block
// As soon as condition variable get signaled, resume the thread and
// again acquire the lock. Then check if condition is met or not
// If condition is met then continue else again go in wait.
m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));
std::cout << "Do Processing On loaded Data" << std::endl;
}
};
int main()
{
Application app;
std::thread thread_1(&Application::mainTask, &app);
std::thread thread_2(&Application::loadData, &app);
thread_2.join();
thread_1.join();
return 0;
}
现在,除了 std::condition_variable m_condVar
它还使用了一个额外的变量 bool m_bDataLoaded
。但在我看来,执行 mainTask
的线程已经被通知数据已通过 std::condition_variable m_condVar
加载。为什么还要检查 bool m_bDataLoaded
以获得相同的信息?比较(同样的代码没有bool m_bDataLoaded
):
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application
{
std::mutex m_mutex;
std::condition_variable m_condVar;
public:
Application()
{
}
void loadData()
{
// Make This Thread sleep for 1 Second
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Loading Data from XML" << std::endl;
// Lock The Data structure
std::lock_guard<std::mutex> guard(m_mutex);
// Notify the condition variable
m_condVar.notify_one();
}
void mainTask()
{
std::cout << "Do Some Handshaking" << std::endl;
// Acquire the lock
std::unique_lock<std::mutex> mlock(m_mutex);
// Start waiting for the Condition Variable to get signaled
// Wait() will internally release the lock and make the thread to block
// As soon as condition variable get signaled, resume the thread and
// again acquire the lock. Then check if condition is met or not
// If condition is met then continue else again go in wait.
m_condVar.wait(mlock);
std::cout << "Do Processing On loaded Data" << std::endl;
}
};
int main()
{
Application app;
std::thread thread_1(&Application::mainTask, &app);
std::thread thread_2(&Application::loadData, &app);
thread_2.join();
thread_1.join();
return 0;
}
- 现在我知道虚假唤醒了,它们本身就需要使用额外的变量。我的问题是 - 他们 只是 的理由吗?如果它们没有发生,是否可以只使用条件变量而不使用任何其他变量(顺便说一句,那不会使 "condition variable" 这个名字用词不当)?
- 另一件事是 - 附加变量的使用不是条件变量也需要互斥锁的唯一原因吗?如果不是,其他原因是什么?
- 如果需要额外的变量(出于虚假唤醒或其他原因)为什么 API 不需要它们(在第二个代码中我不必使用它们来编译代码)? (我不知道其他语言是否一样,所以这个问题可能是 C++-specific。)
不仅仅是虚假唤醒。
当您调用 m_condvar.wait
时,您如何知道您正在等待的条件尚未发生?
可能 'loadData' 已在另一个线程中被调用。当它调用 notify_one()
时,没有任何事情发生,因为没有线程在等待。
现在,如果您拨打 condvar.wait
,您将永远等待,因为没有任何信号通知您。
原版没有这个问题,因为:
- 如果
m_bDataLoaded
为false,则知道数据未加载,after m_bDataLoaded
设置为true,调用者会发出信号条件;
- 锁被持有,我们知道
m_bDataLoaded
在释放之前不能在另一个线程中修改;
condvar.wait
会在释放锁之前将当前线程放入等待队列,所以我们知道m_bDataLoaded
会被置为true在之后我们开始等待,所以notify_one
也会在我们开始等待之后被调用。
回答您的其他问题:
- 是的,与其他变量的协调是条件变量绑定到互斥量的原因。
- API 不需要布尔变量,因为这并不总是您等待的那种条件。
这种事情很常见,例如:
Task *getTask() {
//anyone who uses m_taskQueue or m_shutDown must lock this mutex
unique_lock<mutex> lock(m_mutex);
while (m_taskQueue.isEmpty()) {
if (m_shutdown) {
return null;
}
// this is signalled after a task is enqueued
// or m_shutdown is asserted
m_condvar.wait(lock);
}
return taskQueue.pop_front();
}
这里我们同样要求线程在锁释放前开始等待的critical保证,但是我们等待的条件比较复杂,涉及变量和独立的数据结构,退出方式有多种这段等待。
是的,条件变量只是用来等待一个事件。在我看来,您不应该尝试使用它来控制关键数据结构的并发访问。
我只会说C++。正如您在此处的示例中所见 https://en.cppreference.com/w/cpp/thread/condition_variable/wait,他们使用了此表达式 cv.wait(lk, []{return i == 1;});
。而 []{...}
是无名函数的表达式。所以你也可以自己写一个函数,给函数起个名字:
bool condFn()
{
std::cout << "condFn" << std::endl; // debug output ;)
return i == 1;
}
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cerr << "Waiting... \n";
cv.wait(lk, condFn);
std::cerr << "...finished waiting. i == 1\n";
}
在这个函数中,你可以随心所欲地求值。线程一直处于休眠状态,直到收到通知,然后它始终处理评估继续工作条件的函数。如果为 true,线程将继续,如果为 false,程序将再次休眠。
注意:我将在 C++ 中给出示例,但我相信我的问题与语言无关。如果我错了请纠正我。
只是为了让您真正了解我 - 我想在这里学习的是 该工具的功能,除此之外别无其他。不是它通常用来做什么,也不是约定俗成,而是钝器的作用。在这种情况下 - 条件变量的作用。
到目前为止,在我看来这是一种允许线程等待(阻塞)直到其他线程向它们发出信号(解除阻塞)的简单机制。仅此而已,不处理关键部分访问或数据访问(当然它们可以用于此,但这只是程序员的选择问题)。此外,信号通常仅在发生重要事件(例如加载数据)时才完成,但理论上它可以随时调用。到目前为止正确吗?
现在,我看到的每个示例都使用条件变量对象(例如 std::condition_variable
),但也使用一些附加变量来标记是否发生了某些事情(例如 bool dataWasLoaded
)。从 https://thispointer.com//c11-multithreading-part-7-condition-variables-explained/:
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application
{
std::mutex m_mutex;
std::condition_variable m_condVar;
bool m_bDataLoaded;
public:
Application()
{
m_bDataLoaded = false;
}
void loadData()
{
// Make This Thread sleep for 1 Second
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Loading Data from XML" << std::endl;
// Lock The Data structure
std::lock_guard<std::mutex> guard(m_mutex);
// Set the flag to true, means data is loaded
m_bDataLoaded = true;
// Notify the condition variable
m_condVar.notify_one();
}
bool isDataLoaded()
{
return m_bDataLoaded;
}
void mainTask()
{
std::cout << "Do Some Handshaking" << std::endl;
// Acquire the lock
std::unique_lock<std::mutex> mlock(m_mutex);
// Start waiting for the Condition Variable to get signaled
// Wait() will internally release the lock and make the thread to block
// As soon as condition variable get signaled, resume the thread and
// again acquire the lock. Then check if condition is met or not
// If condition is met then continue else again go in wait.
m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));
std::cout << "Do Processing On loaded Data" << std::endl;
}
};
int main()
{
Application app;
std::thread thread_1(&Application::mainTask, &app);
std::thread thread_2(&Application::loadData, &app);
thread_2.join();
thread_1.join();
return 0;
}
现在,除了 std::condition_variable m_condVar
它还使用了一个额外的变量 bool m_bDataLoaded
。但在我看来,执行 mainTask
的线程已经被通知数据已通过 std::condition_variable m_condVar
加载。为什么还要检查 bool m_bDataLoaded
以获得相同的信息?比较(同样的代码没有bool m_bDataLoaded
):
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application
{
std::mutex m_mutex;
std::condition_variable m_condVar;
public:
Application()
{
}
void loadData()
{
// Make This Thread sleep for 1 Second
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "Loading Data from XML" << std::endl;
// Lock The Data structure
std::lock_guard<std::mutex> guard(m_mutex);
// Notify the condition variable
m_condVar.notify_one();
}
void mainTask()
{
std::cout << "Do Some Handshaking" << std::endl;
// Acquire the lock
std::unique_lock<std::mutex> mlock(m_mutex);
// Start waiting for the Condition Variable to get signaled
// Wait() will internally release the lock and make the thread to block
// As soon as condition variable get signaled, resume the thread and
// again acquire the lock. Then check if condition is met or not
// If condition is met then continue else again go in wait.
m_condVar.wait(mlock);
std::cout << "Do Processing On loaded Data" << std::endl;
}
};
int main()
{
Application app;
std::thread thread_1(&Application::mainTask, &app);
std::thread thread_2(&Application::loadData, &app);
thread_2.join();
thread_1.join();
return 0;
}
- 现在我知道虚假唤醒了,它们本身就需要使用额外的变量。我的问题是 - 他们 只是 的理由吗?如果它们没有发生,是否可以只使用条件变量而不使用任何其他变量(顺便说一句,那不会使 "condition variable" 这个名字用词不当)?
- 另一件事是 - 附加变量的使用不是条件变量也需要互斥锁的唯一原因吗?如果不是,其他原因是什么?
- 如果需要额外的变量(出于虚假唤醒或其他原因)为什么 API 不需要它们(在第二个代码中我不必使用它们来编译代码)? (我不知道其他语言是否一样,所以这个问题可能是 C++-specific。)
不仅仅是虚假唤醒。
当您调用 m_condvar.wait
时,您如何知道您正在等待的条件尚未发生?
可能 'loadData' 已在另一个线程中被调用。当它调用 notify_one()
时,没有任何事情发生,因为没有线程在等待。
现在,如果您拨打 condvar.wait
,您将永远等待,因为没有任何信号通知您。
原版没有这个问题,因为:
- 如果
m_bDataLoaded
为false,则知道数据未加载,afterm_bDataLoaded
设置为true,调用者会发出信号条件; - 锁被持有,我们知道
m_bDataLoaded
在释放之前不能在另一个线程中修改; condvar.wait
会在释放锁之前将当前线程放入等待队列,所以我们知道m_bDataLoaded
会被置为true在之后我们开始等待,所以notify_one
也会在我们开始等待之后被调用。
回答您的其他问题:
- 是的,与其他变量的协调是条件变量绑定到互斥量的原因。
- API 不需要布尔变量,因为这并不总是您等待的那种条件。
这种事情很常见,例如:
Task *getTask() {
//anyone who uses m_taskQueue or m_shutDown must lock this mutex
unique_lock<mutex> lock(m_mutex);
while (m_taskQueue.isEmpty()) {
if (m_shutdown) {
return null;
}
// this is signalled after a task is enqueued
// or m_shutdown is asserted
m_condvar.wait(lock);
}
return taskQueue.pop_front();
}
这里我们同样要求线程在锁释放前开始等待的critical保证,但是我们等待的条件比较复杂,涉及变量和独立的数据结构,退出方式有多种这段等待。
是的,条件变量只是用来等待一个事件。在我看来,您不应该尝试使用它来控制关键数据结构的并发访问。
我只会说C++。正如您在此处的示例中所见 https://en.cppreference.com/w/cpp/thread/condition_variable/wait,他们使用了此表达式 cv.wait(lk, []{return i == 1;});
。而 []{...}
是无名函数的表达式。所以你也可以自己写一个函数,给函数起个名字:
bool condFn()
{
std::cout << "condFn" << std::endl; // debug output ;)
return i == 1;
}
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cerr << "Waiting... \n";
cv.wait(lk, condFn);
std::cerr << "...finished waiting. i == 1\n";
}
在这个函数中,你可以随心所欲地求值。线程一直处于休眠状态,直到收到通知,然后它始终处理评估继续工作条件的函数。如果为 true,线程将继续,如果为 false,程序将再次休眠。