在完整代码(见下文)中,在某些时候(在N次迭代之后)消费者(函数executionServiceHandler
while(inService_)被输入,尽管inService_已被更改(在同一个线程中)[左右似乎是从日志输出]。
我尽可能地试图保护std :: cout,虽然我不确定这是否必要。
你可能会批评代码吗?我知道严格来说,我不需要使用StopServiceCmd让线程完成,但在我看来,我不明白它为什么不能工作。
另外,我本来可以使用std :: function,但是这个例子是从原版中简化的。
我可以补充一点,我在GCC上测试了这个(最新),原来的例子在VCC(2017)上失败了
代码如下(编辑 - 现在使用std :: function:
#include <thread>
#include <functional>
#include <mutex>
#include <iostream>
#include <queue>
#include <sstream>
#include <condition_variable>
class ThreadCmdConsumer
{
public:
using Guard = std::lock_guard<std::mutex>;
using UniqueLock = std::unique_lock<std::mutex>;
ThreadCmdConsumer()
: inService_(),
executionServiceThread_(std::bind(&ThreadCmdConsumer::executionServiceHandler, this))
{
//Wait while thread does not exist...
UniqueLock lock(cmdQMutex_);
while (!inService_) {
conditional_.wait(lock);
}
std::cout << std::hex << this << " constructed and consumer waiting..." << std::endl;
}
~ThreadCmdConsumer() {
static std::size_t nth = 0;
{
UniqueLock lock(cmdQMutex_);
std::cout << "destructing (" << std::dec << nth++ << "): " << std::hex << this << std::endl;
}
process([this](){
//Note: inService_ can only be changed in consumer thread...
inService_ = false;
std::cout << "consumer_->inService state: " << inService_ << std::endl;
});
UniqueLock lock(cmdQMutex_);
std::cout << "producer " << std::hex << this << " destructor has lock" << std::endl;
while (inService_) {
std::cout << "producer " << std::hex << this << " destructor in service, entering wait" << std::endl;
conditional_.wait(lock);
std::cout << "producer " << std::hex << this << " destructor exited wait - has lock" << std::endl;
}
// Join will always succeed as result of StopServiceCmd that sets inService to false
// (in its own thread context). Once join completes, we are certain of executionServiceHandler
// exiting normally.
std::cout << "produces " << std::hex << this << " destructor joining" << std::endl;
lock.unlock();
try {
executionServiceThread_.join();
}
catch (const std::system_error& ex) {
UniqueLock lock(cmdQMutex_);//for cout
std::cout << "Exception during join" << ex.what() << std::endl;
abort();
}
}
void executionServiceHandler()
{
{ //Starts the service...
UniqueLock lock(cmdQMutex_);
inService_ = true;
lock.unlock();
conditional_.notify_one();
}
try {
UniqueLock lock(cmdQMutex_);
while (inService_) {
std::cout << "consumer " << std::hex << this << " has lock" << std::endl;
// Catering for spurious wake-ups too, hence while...
while (cmdQ_.empty()) {
std::cout << "consumer " << std::hex << this << " waiting" << std::endl;
conditional_.wait(lock);
std::cout << "consumer " << std::hex << this << " woken" << std::endl;
}
//Now we have the lock, and queue most certainly not empty
auto cmd = std::move(cmdQ_.front());
cmdQ_.pop();
//@@@lock.unlock(); // Don't want to be locked while executing... removed conservatively
(cmd)();
}
std::cout << "consumer " << std::hex << this << " execution complete" << std::endl;
}
catch(const std::exception& ex) {
std::cerr << "Unexpected " << ex.what() << std::endl;
abort();
}
//Not in service - notify when we've left (then we can truly join...)
conditional_.notify_one();
}
void process(std::function<void()>&& cmd)
{
UniqueLock lock(cmdQMutex_);
std::cout << "producer " << std::hex << this << " has lock" << std::endl;
bool notificationRequired = cmdQ_.empty();
cmdQ_.push(move(cmd));
if (notificationRequired) {
std::cout << "producer " << std::hex << this << " notifying" << std::endl;
lock.unlock();
conditional_.notify_one();
}
else {
std::cout << "producer " << std::hex << this << " not notifying" << std::endl;
}
}
private:
bool inService_;
std::queue<std::function<void()>> cmdQ_;
std::condition_variable conditional_;
std::mutex cmdQMutex_;
std::thread executionServiceThread_;
};
typedef std::function<void(const std::string&)> Handler;
struct ThreadOwner
{
ThreadCmdConsumer executor_;
// Do it done in the context of executor....
void doIt(const Handler& handler)
{ }
};
int main()
{
std::cout << "Program started" << std::endl;
//Sometimes deadlocks on thread being killed
for (int i = 0; i < 1000; ++i) {
auto handler = [](const std::string&){};
{
ThreadOwner owner;
owner.executor_.process([&handler, &owner]() {
owner.doIt(handler);
});
}
}
}