If there is something more difficult than debugging a multithreaded app that is trying to describe the bug itself.
I have two boost::threads (application and display).
Both use the same asio::io_service to do their work.
The display thread has a std::set of type window* which is a class I use to wrap winapi window management.
I use a custom message queue to communicate these two threads.
One of these messages (terminate) is used to notify the display thread that it must not "post" any more methods and that it must call thread_group.remove_thread and remove itself.
The thread has a variable (state) that flags the state of the thread (running, paused, terminated).
If it is running it "posts" it's update() method that iterates an std::set and calls the update method in each window* it contains.
If it is terminated, it clears the std::set, removes itself from the thread_group and doesn't post any more work.
The problem: Once a while, when trying to close the app, the thread's update method gets ran after the thread got "terminated" and the std::set got cleared. Then the update method tries to iterate the std::set and a SIGSEGV takes place. This only happens 1 every 10 runs of the application and I'm having a hard time trying to guess what's wrong.
I'll try to post the relevant code, if more is needed I'll try to add it.
int main(int argc, char **argv)
{
boost::asio::io_service ios;
boost::asio::strand strand(ios);
boost::thread_group threads;
owl::system::pump pump;
application app(&threads, &strand, &pump);
owl::system::display display(&strand, &pump);
ios.run();
threads.join_all();
return 0;
}
...
void display::on_terminate()
{
close_all_windows();
}
...
void display::close_all_windows()
{
windows.move_first();
while (!windows.eof())
{
window* win = windows.value();
win->destroy();
delete win;
windows.move_next();
}
windows.clear();
check_no_window();
}
...
void display::on_update()
{
if (windows.size())
{
windows.move_first();
while (!windows.eof())
{
windows.value()->update();
windows.move_next(); // Here happens the SIGSEGV
}
}
}
The class display inherits the class subsystem that manages thread execution. This is the relevant code involving the execution of on_update()
void subsystem::do_update()
{
message* msg;
size_t message_count = messages.size();
for (size_t i=0; i<message_count; i++)
{
msg = messages[i];
process_message(msg);
strand->dispatch(strand->wrap(boost::bind(&message::deallocate, msg)));
}
switch (state)
{
case running:
{
on_update();
}
break;
case paused:
{
// Do not update. Just check the queue and sleep
sleep(10);
}
break;
case terminated:
{
do_terminate();
return;
}
break;
}
strand->post(strand->wrap(boost::bind(&subsystem::check_for_messages, this)));
}
void subsystem::check_for_messages()
{
messages.clear();
pump->get_messages(this, messages);
ios->post(boost::bind(&subsystem::do_update, this));
}
The SIGSEGV occurs exactly when trying to increment the std::set iterator.
Child process PID: 2272
Program received signal SIGSEGV, Segmentation fault.
In std::_Rb_tree_increment(std::_Rb_tree_node_base const*) ()
stl_tree.h:269
Related
I am a hobby programmer learning C++ and multi-threading, and getting started on my first thread-pool attempt.
I use Code::Blocks 20.3, wxWidgets 3.1.4, and MinGW 17.1 on a Windows 10 Pro computer.
I have tried several thread-pool examples, but all blocked the GUI.
I found an example shown in https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication
that uses detached threads in a pool. This should not block the GUI.
I have "restructured" the 1 file example to work in a test project (gui, app, main, thread-pool modules).
I placed the classes in their own file, and moved the "main" part to the Main.cpp in my test project and replaced the gui code with a separate class file.
The standard example works as expected.
In the example, strings are passed to the thread-pool and other strings back to the main thread.
I have been searching for main thread AddToQueue() to pass any function like e.g. aTask() (void, or returning something to the main thread) that executes in the thread-pool. My search was not successful :-(.
=== Simple Task to be executed in a thread ===
std::vector<wxString> wxThreadCom2Frame::aTask(wxString wsSomeString, int x)
{
std::vector<wxString> vTest{};
for(int i = 0; i < x; i++)
{
wxString wsTest{};
wsTest << wsSomeString << " [" << i << "]";
vTest.push_back(wsTest);
}
return vTest;
}
=== Or as alternative, pass the vector by reference
aTask(_T("Just some text"), 5, &vTest); // to be queued with AddJob
===
void wxThreadCom2Frame::aTask(wxString wsSomeString, int x, std::vector<wxString> *vTest)
{
for(int i = 0; i < x; i++)
{
wxString wsTest{};
wsTest << wsSomeString << " [" << i << "]\n";
vTest->push_back(wsTest);
}
}
===
I hope someone can help me understand how to do this.
This is the first step to what I actually like to achieve.
An 'extraction' function returns a structure of 20 tags from a music file (mp3, flac, etc).
The main 'collecting' function will call the 'extraction' function for each file (up to 7000) in a list and place it in the queue of the thread-pool.
The 'collecting' function returns a vector of structures to the main thread for further processing.
Regards, Ruud.
=== ThreadCom.cpp ===
/////////////////////////////////////////////////////////////////////////////
// https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication //
/////////////////////////////////////////////////////////////////////////////
// Standard
#include <stdlib.h>
#include <assert.h>
#include <map>
#include <list>
// wxWidgets
#include <wx/frame.h>
#include <wx/thread.h>
#include <wx/menu.h>
#include <wx/app.h>
class tJOB
{
public:
enum tCOMMANDS // list of commands that are currently implemented
{
eID_THREAD_EXIT=wxID_EXIT, // thread should exit or wants to exit
eID_THREAD_NULL=wxID_HIGHEST+1, // dummy command
eID_THREAD_STARTED, // worker thread has started OK
eID_THREAD_JOB = ID_THREAD_JOB, // process normal job
eID_THREAD_JOBERR = ID_THREAD_JOBERR // process erroneous job after which thread likes to exit
}; // enum tCOMMANDS
tJOB() : m_cmd(eID_THREAD_NULL) {}
tJOB(tCOMMANDS cmd, const wxString& arg) : m_cmd(cmd), m_Arg(arg) {}
tCOMMANDS m_cmd; wxString m_Arg;
}; // class tJOB
class QUEUE
{
public:
enum tPRIORITY { eHIGHEST, eHIGHER, eNORMAL, eBELOW_NORMAL, eLOW, eIDLE }; // priority classes
QUEUE(wxEvtHandler* pParent) : m_pParent(pParent) {}
void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL) // push a job with given priority class onto the FIFO
{
wxMutexLocker lock(m_MutexQueue); // lock the queue
m_Jobs.insert(std::make_pair(priority, job)); // insert the prioritized entry into the multimap
m_QueueCount.Post(); // new job has arrived: increment semaphore counter
} // void AddJob(const tJOB& job, const tPRIORITY& priority=eNORMAL)
tJOB Pop()
{
tJOB element;
m_QueueCount.Wait(); // wait for semaphore (=queue count to become positive)
m_MutexQueue.Lock(); // lock queue
element=(m_Jobs.begin())->second; // get the first entry from queue (higher priority classes come first)
m_Jobs.erase(m_Jobs.begin()); // erase it
m_MutexQueue.Unlock(); // unlock queue
return element; // return job entry
} // tJOB Pop()
void Report(const tJOB::tCOMMANDS& cmd, const wxString& sArg=wxEmptyString, int iArg=0) // report back to parent
{
wxCommandEvent evt(wxEVT_THREAD, cmd); // create command event object
evt.SetString(sArg); // associate string with it
evt.SetInt(iArg);
m_pParent->AddPendingEvent(evt); // and add it to parent's event queue
} // void Report(const tJOB::tCOMMANDS& cmd, const wxString& arg=wxEmptyString)
size_t Stacksize() // helper function to return no of pending jobs
{
wxMutexLocker lock(m_MutexQueue); // lock queue until the size has been read
return m_Jobs.size();
}
private:
wxEvtHandler* m_pParent;
std::multimap<tPRIORITY, tJOB> m_Jobs; // multimap to reflect prioritization: values with lower keys come first, newer values with same key are appended
wxMutex m_MutexQueue; // protects queue access
wxSemaphore m_QueueCount; // semaphore count reflects number of queued jobs
};
class WorkerThread : public wxThread
{
public:
WorkerThread(QUEUE* pQueue, int id=0) : m_pQueue(pQueue), m_ID(id) { assert(pQueue); wxThread::Create(); }
private:
QUEUE* m_pQueue;
int m_ID;
virtual wxThread::ExitCode Entry()
{
Sleep(1000); // sleep a while to simulate some time-consuming init procedure
tJOB::tCOMMANDS iErr;
m_pQueue->Report(tJOB::eID_THREAD_STARTED, wxEmptyString, m_ID); // tell main thread that worker thread has successfully started
try { while(true) OnJob(); } // this is the main loop: process jobs until a job handler throws
catch(tJOB::tCOMMANDS& i) { m_pQueue->Report(iErr=i, wxEmptyString, m_ID); } // catch return value from error condition
return (wxThread::ExitCode)iErr; // and return exit code
} // virtual wxThread::ExitCode Entry()
virtual void OnJob()
{
tJOB job=m_pQueue->Pop(); // pop a job from the queue. this will block the worker thread if queue is empty
switch(job.m_cmd)
{
case tJOB::eID_THREAD_EXIT: // thread should exit
Sleep(1000); // wait a while
throw tJOB::eID_THREAD_EXIT; // confirm exit command
case tJOB::eID_THREAD_JOB: // process a standard job
Sleep(2000);
m_pQueue->Report(tJOB::eID_THREAD_JOB, wxString::Format(wxT("Job #%s done."), job.m_Arg.c_str()), m_ID); // report successful completion
break;
case tJOB::eID_THREAD_JOBERR: // process a job that terminates with an error
m_pQueue->Report(tJOB::eID_THREAD_JOB, wxString::Format(wxT("Job #%s erroneous."), job.m_Arg.c_str()), m_ID);
Sleep(1000);
throw tJOB::eID_THREAD_EXIT; // report exit of worker thread
break;
case tJOB::eID_THREAD_NULL: // dummy command
default:
break; // default
} // switch(job.m_cmd)
} // virtual void OnJob()
}; // class WorkerThread : public wxThread
=== partial wxThreadCom2Main.cpp ===
void wxThreadCom2Frame::AddToQueue( wxCommandEvent& event )
{
int iJob=rand();
m_pQueue->AddJob(tJOB((tJOB::tCOMMANDS)event.GetId(), wxString::Format(wxT("%u"), iJob)));
SetStatusText(wxString::Format(wxT("Job #%i started."), iJob)); // just set the status text
}
void wxThreadCom2Frame::OnThread(wxCommandEvent& event) // handler for thread notifications
{
switch(event.GetId())
{
case tJOB::eID_THREAD_JOB:
// Get the returned vector and do something with it
SetStatusText(wxString::Format(wxT("[%i]: %s"), event.GetInt(), event.GetString().c_str())); // progress display
break;
case tJOB::eID_THREAD_EXIT:
SetStatusText(wxString::Format(wxT("[%i]: Stopped."), event.GetInt()));
m_Threads.remove(event.GetInt()); // thread has exited: remove thread ID from list
if(m_Threads.empty()) { EnableControls(false); } // disable some menu items if no more threads
break;
case tJOB::eID_THREAD_STARTED:
SetStatusText(wxString::Format(wxT("[%i]: Ready."), event.GetInt()));
EnableControls(true); // at least one thread successfully started: enable controls
break;
default:
event.Skip();
}
}
void wxThreadCom2Frame::EnableControls(bool bEnable) // en/dis-able Stop, Add Job, Add JobErr
{
wxMenu* pMenu=GetMenuBar()->GetMenu(0);
static const int MENUIDS[]={/*ID_START_THREAD, */ID_THREAD_EXIT, ID_THREAD_JOB, ID_THREAD_JOBERR};
for(unsigned int i=0; i<WXSIZEOF(MENUIDS); pMenu->Enable(MENUIDS[i++], bEnable));
}
===
I am trying to make a snake game in c++. I have a thread that moves the snake's head in the direction that the user specifies with the arrow keys. if I press the up arrow key I need the head to move up and continue moving (this is done by the thread) without me pressing the arrow keys.
The issue is i can't get the keyboard input to work together with the thread, its either one function works or its the other, they can't work together
Keyboard input function:
void moveSnake()
{
while(true)
{
int key = getch();
switch(key)
{
case 72:
{
//UP
break;
}
case 80:
{
// DOWN
break;
}
case 77:
{
// RIGHT
break;
}
case 75:
{
// LEFT
break;
}
}
}
Thread function:
void thread_handler()
{
while(true)
{
// Move the snake
Sleep(500);
}
}
This is where i create the thread and call the keyboard function
int main()
{
moveSnake();
thread t(thread_handler);
t.join();
return 0;
}
I have tried
putting moveSnake() in a thread
executing the thread_handler function without a thread
tried to put both functions in a separate thread
tried switching the order with all these possible solutions
But none of these worked.
I need a way to get the keycode for the arrow keys while running the thread at the same time
Follow your source code like the compiler would, from top to bottom. Your debugger can help you step through it. You've went into a never ending loop in your main thread. What comes after that doesn't matter because you never get there.
Even if you switch the ordering of things, you've still got a never ending loop, so you cannot join properly after you call it.
You need to read up on some basic threading lessons.
Just because you are using threads doesn't mean you can stop worrying about whether loops exit or not. Loops should always have some exit condition.
For your program, I'd assume you'd want to start a thread to handle the input, lock some manner of storage and read key presses. Back in your main thread, you'd probably want to loop until some signal for exit, lock and get the stored key press/command (maybe use a thread safe queue?), and react to it.
Here is a very simple and naive example:
#include <mutex>
#include <thread>
#include <queue>
#include <conio.h>
#include <iostream>
class Application
{
public:
void Run()
{
// Start input thread
if (m_inputThread.joinable())
{
Shutdown();
}
auto thread = std::thread(std::bind(&Application::InputThreadProc, this));
m_inputThread.swap(thread);
while (!m_shutdown)
{
// React to the queued input, including checking for shutdown
// I'm just going to ignore the complex issue of timing
std::this_thread::sleep_for(std::chrono::milliseconds(33));
std::lock_guard<std::mutex> lock(m_mutex);
if( !m_keysPressed.empty() )
{
auto key = m_keysPressed.front();
m_keysPressed.pop();
switch (key)
{
case 75:
m_shutdown = true;
default:
std::cout << "I saw a key: " << key << std::endl;
break;
}
}
}
Shutdown();
}
private:
std::mutex m_mutex;
std::queue<int> m_keysPressed;
bool m_shutdown = false;
std::thread m_inputThread;
void Shutdown()
{
if (m_inputThread.joinable())
{
m_inputThread.join();
}
}
void InputThreadProc()
{
while (!m_shutdown)
{
std::this_thread::sleep_for(std::chrono::milliseconds(33));
std::lock_guard<std::mutex> lock(m_mutex);
// OS Specific. Better means of getting localized input exist.
// Also, timing is an issue. Is the key pressed when we are trying to read.
// Input is a complex topic and many libraries for it exist
m_keysPressed.emplace(_getch());
}
}
};
int main()
{
Application application;
application.Run();
return 0;
}
Change the order of your main to
int main()
{
thread t(thread_handler);
moveSnake();
t.join();
return 0;
}
this way the thread is started before your input loop (which is an endless loop). Make sure you leave the t.join() at the end.
With this said, you need to create a method that both the thread and input loop exit. Currently both are infinite loops.
I want to run boost::asio::io_service.run() in a background thread. So when I need it post() func into.
This is main func:
int main(int /*argc*/, char** /*argv*/)
{
std::string message = "hello";
logg = new logger_client(filename,ip,13666);
logg->start();
while (true)
logg->add_string(message);
return 0;
}
And some relevant funcs from logger_client:
std::auto_ptr<boost::asio::io_service::work> work;
logger_client::logger_client(std::string& filename,std::string& ip, uint16_t port) : work(new boost::asio::io_service::work(io_service))
{
}
void logger_client::start()
{
ios_thread = new boost::thread(boost::bind(&io_service.run,&io_service));
}
void print_nothing()
{
printf("%s\n","lie");
}
void logger_client::add_string(std::string& message)
{
io_service.post(boost::bind(print_nothing));
//io_service.post(strand->wrap(boost::bind(&logger_client::add_string_imp,this,message)));
//io_service.run();
}
When i run this, my program eats 2Gb less than a minute. If i remove endless work and change to this:
void logger_client::add_string(std::string& message)
{
io_service.post(boost::bind(print_nothing));
//io_service.post(strand->wrap(boost::bind(&logger_client::add_string_imp,this,message)));
io_service.run();
}
Program works just fine. But I don't want to invoke async operations on this (main) thread. What am i doing wrong?
UPDATE
I added sleep(1sec) in while(true) loop and memory is no longer growing. But this is not a solution. Because if I call run() after post() (i.e. use main thread for processing handles) and even add five more threads with while(true) loops memory is not growing. So why main thread is so much better than newly created? I also tried thread pool for io_service::run - did not help.
io_service.run will exit unless there are pending operations.
Therefore, your ios_thread will exit immediately.
The solution is to use io_service::work.
In addition, endless loop spam like this
while (true)
logg->add_string(message);
is not a good idea, maybe add some sleep(), to slow it down a bit and keep it under control.
Hy,
I'm writing my first Qt program and getting now in troubles with:
QObject::killTimer: timers cannot be stopped from another thread
QObject::startTimer: timers cannot be started from another thread
My program will communicate to a CANOpen bus for that I'm using the Canfestival Stack. The Canfestival will work with callback methods. To detects timeout in communication I setup a timer function (somehow like a watchdog). My timer package consist out of a "tmr" module, a "TimerForFWUpgrade" module and a "SingleTimer" module. The "tmr" module was originally C programmed so the static "TimerForFWUpgrade" methods will interface it. The "tmr" module will be part of a C programed Firmware update package.
The timer will work as follows. Before a message is sent I will call TMR_Set method. An then in my idle program loop with TMR_IsElapsed we check for a timer underflow. If TMR_IsElapsed I will do the errorhandling. As you see the TMR_Set method will be called continuously and restart the QTimer again and again.
The above noted errors are appearing if I start my program. Can you tell me if my concept could work? Why does this errors appear? Do I have to use additional threads (QThread) to the main thread?
Thank you
Matt
Run and Idle loop:
void run
{
// start communicate with callbacks where TMR_Set is set continously
...
while(TMR_IsElapsed(TMR_NBR_CFU) != 1);
// if TMR_IsElapsed check for errorhandling
....
}
Module tmr (interface to C program):
extern "C"
{
void TMR_Set(UINT8 tmrnbr, UINT32 time)
{
TimerForFWUpgrade::set(tmrnbr, time);
}
INT8 TMR_IsElapsed(UINT8 tmrnbr)
{
return TimerForFWUpgrade::isElapsed(tmrnbr);
}
}
Module TimerForFWUpgrade:
SingleTimer* TimerForFWUpgrade::singleTimer[NR_OF_TIMERS];
TimerForFWUpgrade::TimerForFWUpgrade(QObject* parent)
{
for(unsigned char i = 0; i < NR_OF_TIMERS; i++)
{
singleTimer[i] = new SingleTimer(parent);
}
}
//static
void TimerForFWUpgrade::set(unsigned char tmrnbr, unsigned int time)
{
if(tmrnbr < NR_OF_TIMERS)
{
time *= TimerForFWUpgrade::timeBase;
singleTimer[tmrnbr]->set(time);
}
}
//static
char TimerForFWUpgrade::isElapsed(unsigned char tmrnbr)
{
if(true == singleTimer[tmrnbr]->isElapsed())
{
return 1;
}
else
{
return 0;
}
}
Module SingleTimer:
SingleTimer::SingleTimer(QObject* parent) : QObject(parent),
pTime(new QTimer(this)),
myElapsed(true)
{
connect(pTime, SIGNAL(timeout()), this, SLOT(slot_setElapsed()));
pTime->setTimerType(Qt::PreciseTimer);
pTime->setSingleShot(true);
}
void SingleTimer::set(unsigned int time)
{
myElapsed = false;
pTime->start(time);
}
bool SingleTimer::isElapsed()
{
QCoreApplication::processEvents();
return myElapsed;
}
void SingleTimer::slot_setElapsed()
{
myElapsed = true;
}
Use QTimer for this purpose and make use of SIGNALS and SLOT for the purpose of starting and stopping the timer/s from different threads. You can emit the signal from any thread and catch it in the thread which created the timer to act on it.
Since you say you are new to Qt, I suggest you go through some tutorials before proceeding so that you will know what Qt has to offer and don't end up trying to reinvent the wheel. :)
VoidRealms is a good starting point.
You have this problem because the timers in the static array is created in Thread X, but started and stopped in Thread Y. This is not allowed, because Qt rely on thread affinity to timeout timers.
You can either create, start stop in the same thread or use signal and slots to trigger start and stop operations for timers. The signal and slot solution is a bit problematic Because you have n QTimer objects (Hint: how do you start the timer at position i?)
What you can do instead is create and initialize the timer at position tmrnbr in
TimerForFWUpgrade::set(unsigned char tmrnbr, unsigned int time)
{
singleTimer[tmrnbr] = new SingleTimer(0);
singleTimer[tmrnbr]->set(time);
}
which is executed by the same thread.
Futhermore, you don't need a SingleTimer class. You are using Qt5, and you already have all you need at your disposal:
SingleTimer::isElapsed is really QTimer::remainingTime() == 0;
SingleTimer::set is really QTimer::setSingleShot(true); QTimer::start(time);
SingleTimer::slot_setElapsed becomes useless
ThusSingleTimer::SingleTimer becomes useless and you dont need a SingleTimer class anymore
I got the errors away after changing my timer concept. I'dont use anymore my SingleTimer module. Before the QTimer I won't let timeout and maybe because of that I run into problems. Now I have a cyclic QTimer that times out every 100ms in slot function I will then count the events. Below my working code:
TimerForFWUpgrade::TimerForFWUpgrade(QObject* parent) : QObject(parent),
pTime(new QTimer(this))
{
connect(pTime, SIGNAL(timeout()), this, SLOT(slot_handleTimer()));
pTime->setTimerType(Qt::PreciseTimer);
pTime->start(100);
}
void TimerForFWUpgrade::set(unsigned char tmrnbr, unsigned int time)
{
if(tmrnbr < NR_OF_TIMERS)
{
if(timeBase != 0)
{
myTimeout[tmrnbr] = time / timeBase;
}
else
{
myTimeout[tmrnbr] = 0;
}
myTimer[tmrnbr] = 0;
myElapsed[tmrnbr] = false;
myActive[tmrnbr] = true;
}
}
char TimerForFWUpgrade::isElapsed(unsigned char tmrnbr)
{
QCoreApplication::processEvents();
if(tmrnbr < NR_OF_TIMERS)
{
if(true == myElapsed[tmrnbr])
{
return 1;
}
else
{
return 0;
}
}
else
{
return 0; // NOK
}
}
void TimerForFWUpgrade::slot_handleTimer()
{
for(UINT8 i = 0; i < NR_OF_TIMERS; i++)
{
if(myActive[i] == true)
{
myTimer[i]++;
if(myTimeout[i] < myTimer[i])
{
myTimer[i] = 0;
myElapsed[i] = true;
myActive[i] = false;
}
}
}
}
I've a for loop that will launch processes in parallel every launched process will return a response back indicating that it is ready. I want to wait for the response and I'll abort if a certain timeout is reached.
Development environment is VS2008
Here is the pseudo code:
void executeCommands(std::vector<Command*> commands)
{
#pragma omp parallel for
for (int i = 0; i < commands.size(); i++)
{
Command* cmd = commands[i];
DWORD pid = ProcessLauncher::launchProcess(cmd->getWorkingDirectory(), cmd->getCommandToExcecute(), cmd->params);
//Should I wait for process to become ready?
if (cmd->getWaitStatusTimeout() > 0)
{
ProcessStatusManager::getInstance().addListener(*this);
//TODO: emit process launching signal
//BEGINNING OF QUESTION
//I don't how to do this part.
//I might use QT's QWaitCondition but if there is another solution in omp
//I'd like to use it
bool timedOut;
SOMEHANDLE handle = Openmp::waitWithTimeout(cmd->getWaitStatusTimeout(), &timedOut);
mWaitConditions[pid]) = handle;
//END OF QUESTION
if (timedOut)
{
ProcessStatusManager::getInstance().removeListener(*this);
//TODO: kill process
//TODO: emit fail signal
}
else
{
//TODO: emit process ready signal
}
}
else
{
//TODO: emit process ready signal
}
}
}
void onProcessReady(DWORD sourceProcessPid)
{
ProcessStatusManager::getInstance().removeListener(*this);
SOMEHANDLE handle = mWaitConditions[sourceProcessPid];
if (mWaitConditions[sourceProcessPid] != 0)
{
Openmp::wakeAll(handle);
}
}
As the comment above pointed out, Michael Suess did present a paper on adding this functionality to OpenMP. He is the last of several people that have proposed adding some type of wait function to OpenMP. The OpenMP language committee has taken the issue up several times. Each time it has been rejected because there are other ways to do this function already. I don't know Qt, but as long as the functions it provides are thread safe, then you should be able to use them.