简体   繁体   中英

Cross platform Event handling - std::condition_variable wait_for seems ignores timeout

I'm porting some code that uses native MS API and I've implemented something that tries to mimic event handling with CreateEvent , SetEvent , WaitForSingleObject , WaitForMultipleObjects , etc...

I've used std::condition_variable as in my case it only needs to work in the same application for thread synchronization.

In my test app everything seems to be working as it should but when I try the same code in my production code calls to wait_for break the code although no exceptions occur. It just seems to carry on when I am stepping through it and then it never times out. I have all exception turned on in the debug configuration and nothing is signaled. I have no idea what is causing this...

I am testing this in VS2013 in a Win7 machine

The code is divided into two classes Handler and Event , where Handler holds a fixed array of Event . Event::WaitForSingleObject is where I actually wait on the condition_variable and it can either be called directly or called from Handler::WaitForMultipleObjects with a minimal timeout so that all events in a list are verified.

I have multiple threads accessing the Handler object but the array elements can't be accessed at the same time (this is by design), and as such access to the array isn't protected.

Here is the the header (concurrency2.h) file that implements the code that tries to handle event handling:

 #ifndef AAAAAA #define AAAAAA #include<thread> #include<future> #include<mutex> #include<array> static const unsigned k_INFINITE = 0xFFFFFFFF; namespace cross { class Event; using EventId = int; //prefer alias declaration to typedefs typedef Event* Event_handle; class Event { std::string m_name; //notification agent for event notification (this is what other threads will use to notify the event) std::mutex m_cvmutex; std::condition_variable m_cv; bool m_signaled; bool m_waiting; //notification agents for event termination (used internally to force an event to be signaled so we can kill the event) std::mutex m_tcvmutex; std::condition_variable m_tcv; bool m_tcv_signaled; bool m_terminating; Event(void) {} int SignalTermination() { m_tcv.notify_all(); m_tcv_signaled = true; return -2; } void WaitForTermination() { if (!m_tcv_signaled) { std::unique_lock<std::mutex> lk(m_tcvmutex); m_tcv.wait(lk); //wait for ever lk.unlock(); } } public: Event(std::string _name) : m_name(_name), m_signaled(false), m_waiting(false), m_terminating(false), m_tcv_signaled(false) {} void SetEvent(bool kill=false) { m_cv.notify_all(); m_signaled = true; m_terminating = kill; std::cout << name() << " notify... " << std::endl; if (m_terminating) { WaitForTermination(); } } void ResetEvent() { m_signaled = false; } bool IsWaiting() { return m_waiting; } const char* name() { return m_name.c_str(); } /* returns: 0 if event was triggered -1 if timeout has occured -2 if I'm trying to get rid of this */ int WaitForSingleObject(unsigned timeout = k_INFINITE) { if (m_terminating) { return SignalTermination(); } int ret = -1; if (m_signaled) { ret = 0; //m_set_before = false; } else { m_waiting = true; std::unique_lock<std::mutex> lk(m_cvmutex, std::try_to_lock); //std::cout << "wait... " ; if (timeout == k_INFINITE) { m_cv.wait(lk); ret = 0; } else { auto wait = m_cv.wait_for(lk, std::chrono::milliseconds(timeout)); if (wait == std::cv_status::timeout) { ret = -1; } else if (wait == std::cv_status::no_timeout) { ret = 0; } } lk.unlock(); m_waiting = false; } if (m_terminating) { ret = SignalTermination(); } return ret; } }; class Handler { public: Handler() : count(0), wait_for_multiple_objects_timeout(100) { m_events.fill(nullptr); } ~Handler() { //for (const auto& ev : m_events) for (auto ev : m_events) { delete ev; //ev = NULL; this doesn't work inside a range based loop } } EventId CreateEvent(char* name) { m_events[count] = new Event(name); return count++; //the event id will be the index in the events vector. TODO This needs to be smarter! } void SetEvent(EventId eid) { if (eid < count && m_events[eid] != nullptr) { m_events[eid]->SetEvent(); } } void ResetEvent(EventId eid) { if (eid < count && m_events[eid] != nullptr) { m_events[eid]->ResetEvent(); } } void ResetEvents() { for (auto ev : m_events) { if (ev != nullptr) ev->ResetEvent(); } } bool CloseHandle(EventId eid) { if (eid < count && m_events[eid] != nullptr) { if (m_events[eid]->IsWaiting()) { //if it's waiting, set the event and signal it to be killed. //because we are trying to kill the even, SetEvent will block until the event is dead m_events[eid]->SetEvent(true); } delete m_events[eid]; m_events[eid] = nullptr; return true; } return false; } void CloseAllHandles() { for (int i = 0; i < 100; ++i) { if (m_events[i] != nullptr) { CloseHandle(i); } } } int WaitForSingleObject(EventId eid, unsigned timeout = k_INFINITE) { if (eid < count && m_events[eid] != nullptr) { return m_events[eid]->WaitForSingleObject(timeout); } return -1; } EventId WaitForMultipleObjects(unsigned count, EventId events[], bool wait_for_all=false, unsigned timeout = k_INFINITE) { /* timeout is the value that we must wait until at least one (or all if wait_for_all is true), of the events is triggered. Each event will wait for 100 miliseconds in order to allow all events to be verified. timeout is used for each of the events in the list and is not a global value. */ std::vector<unsigned> timeouts; std::vector<bool> signaled; for (unsigned i = 0; i < count; ++i) { timeouts.push_back(0); signaled.push_back(false); } bool wait_for_ever = timeout == k_INFINITE; do { for (unsigned i = 0; i < count; ++i) { EventId result = m_events[events[i]]->WaitForSingleObject(wait_for_multiple_objects_timeout); if (result == -2) { return -2; } else if (result == -1) { //timeout waiting for events[i] if (!wait_for_ever) { timeouts[i] += wait_for_multiple_objects_timeout; if (timeouts[i] >= timeout) { //as soon as one of the events timeout, they are all timedout. This can be different if we need it to... return -1; } } } else { signaled[i] = true; if (!wait_for_all) return events[i]; bool all_signaled = true; for (auto sig : signaled) { if (sig == false) { all_signaled = false; break; } } if (all_signaled) { return events[i]; //return last signaled event when all events are signaled } } } } while (1); return -1; } const char* GetName(EventId eid) { if (eid < count && m_events[eid] != nullptr) { return m_events[eid]->name(); } return ""; } private: int count; std::array<Event_handle, 100> m_events; unsigned wait_for_multiple_objects_timeout; }; } //end namespace cross #endif 

And here is main.cpp

 // BackToTheFuture.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<thread> #include<iostream> #include<mutex> #include <string> #include <sstream> #include "concurrency2.h" using namespace std; cross::Handler handler; cross::EventId events[6]; void mythread() { int roll_index = 0; do { try { cross::EventId signaled = handler.WaitForMultipleObjects(3, events, true);// false, 5000); if (signaled >= 0) { cout << handler.GetName(signaled) << " signaled@1" << endl; break; } else if (signaled == -1) { cout << handler.GetName(roll_index) << " time out@1" << endl; break; } else if (signaled == -2) break; } catch (...) //need to handle this properly, for now will do { cout << "exception..." << endl; } if (++roll_index > 2) roll_index = 0; } while (1); cout << "EXITED thread 1\\n"; } void mythread2() { int roll_index = 0; do { try { //WaitForMultipleObjects test - pass an array of EventIds cross::EventId signaled = handler.WaitForMultipleObjects(3, events+3, true);// , false, 5000); if (signaled >= 0) { cout << handler.GetName(signaled) << " signaled@2" << endl; break; } else if (signaled == -1) { cout << handler.GetName(roll_index) << " time out@2" << endl; break; } else if (signaled == -2) break; } catch (...) //need to handle this properly, for now will do { cout << "exception..." << endl; } if (++roll_index > 2) roll_index = 0; } while (1); cout << "EXITED thread 2\\n"; } int _tmain(int argc, _TCHAR* argv[]) { events[0] = handler.CreateEvent("event 0"); events[1] = handler.CreateEvent("event 1"); events[2] = handler.CreateEvent("event 2"); events[3] = handler.CreateEvent("event a"); events[4] = handler.CreateEvent("event b"); events[5] = handler.CreateEvent("event c"); std::thread t1(mythread); std::thread t2(mythread2); string input = ""; int myNumber = 0; bool _exit = false; do { getline(cin, input); // This code converts from string to number safely. stringstream myStream(input); if (myStream >> myNumber) { if (myNumber > 5) { handler.CloseAllHandles(); _exit = true; handler.SetEvent(0); } else { handler.SetEvent(myNumber); } } } while (!_exit); t1.join(); t2.join(); cout << "Terminated... press any key to continue..." << endl; getline(cin, input); return 0; } 

Any help would be appreciated!

Cheers, Andre

I'm not sure if this is the cause however, I think the following line:

std::unique_lock<std::mutex> lk(m_cvmutex, std::try_to_lock);

Shouldn't use the std::try_to_lock parameter as it doesn't block to acquire the lock -- although I don't see where in your example this could fail... if for example more than one thread was calling WaitForSingleObject on the same Event instance, the lk might not acquire the mutex and the following std::wait_for could exhibit "undefined behaviour".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM