174 строки
5.7 KiB
C++
174 строки
5.7 KiB
C++
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
|
//
|
|
// simplethread.h -- a simple thread implementation
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include "basetypes.h"
|
|
#ifdef _WIN32
|
|
#include <process.h> // for _beginthread()
|
|
#endif
|
|
|
|
namespace msra { namespace util {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// signallingevent -- wrapper around Windows events
|
|
// ---------------------------------------------------------------------------
|
|
class signallingevent // TODO: should this go into basetypes.h?
|
|
{
|
|
HANDLE h;
|
|
|
|
public:
|
|
signallingevent(bool initialstate = true)
|
|
{
|
|
h = ::CreateEvent(NULL, FALSE /*manual reset*/, initialstate ? TRUE : FALSE, NULL);
|
|
if (h == NULL)
|
|
throw std::runtime_error("signallingevent: CreateEvent() failed");
|
|
}
|
|
~signallingevent()
|
|
{
|
|
::CloseHandle(h);
|
|
}
|
|
void wait()
|
|
{
|
|
if (::WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0)
|
|
throw std::runtime_error("wait: WaitForSingleObject() unexpectedly failed");
|
|
}
|
|
void flag()
|
|
{
|
|
if (::SetEvent(h) == 0)
|
|
throw std::runtime_error("flag: SetEvent() unexpectedly failed");
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// simplethread -- simple thread wrapper
|
|
// ---------------------------------------------------------------------------
|
|
class simplethread : CCritSec
|
|
{
|
|
std::shared_ptr<std::exception> badallocexceptionptr; // in case we fail to copy the exception
|
|
std::shared_ptr<std::exception> exceptionptr; // if non-NULL, then thread failed with exception
|
|
// wrapper around passing the functor
|
|
signallingevent startsignal;
|
|
const void *functorptr;
|
|
template <typename FUNCTION>
|
|
static unsigned int __stdcall staticthreadproc(void *usv)
|
|
{
|
|
simplethread *us = (simplethread *) usv;
|
|
const FUNCTION body = *(const FUNCTION *) us->functorptr;
|
|
us->startsignal.flag();
|
|
us->threadproc(body);
|
|
return 0;
|
|
}
|
|
template <typename FUNCTION>
|
|
void threadproc(const FUNCTION &body)
|
|
{
|
|
try
|
|
{
|
|
body(); // execute the function
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
fail(e);
|
|
}
|
|
catch (...) // we do not catch anything that is not based on std::exception
|
|
{
|
|
fprintf(stderr, "simplethread: thread proc failed with unexpected unknown exception, which is not allowed. Terminating\n");
|
|
fflush(stderr); // (needed?)
|
|
abort(); // should never happen
|
|
}
|
|
}
|
|
HANDLE threadhandle;
|
|
|
|
public:
|
|
template <typename FUNCTION>
|
|
simplethread(const FUNCTION &body)
|
|
: badallocexceptionptr(new std::bad_alloc()), functorptr(&body), startsignal(false)
|
|
{
|
|
unsigned int threadid;
|
|
uintptr_t rc = _beginthreadex(NULL /*security*/, 0 /*stack*/, staticthreadproc<FUNCTION>, this, CREATE_SUSPENDED, &threadid);
|
|
if (rc == 0)
|
|
throw std::runtime_error("simplethread: _beginthreadex() failed");
|
|
threadhandle = OpenThread(THREAD_ALL_ACCESS, FALSE, threadid);
|
|
if (threadhandle == NULL)
|
|
throw std::logic_error("simplethread: _beginthreadex() unexpectedly did not return valid thread id"); // BUGBUG: leaking something
|
|
DWORD rc1 = ::ResumeThread(threadhandle);
|
|
if (rc1 == (DWORD) -1)
|
|
{
|
|
::TerminateThread(threadhandle, 0);
|
|
::CloseHandle(threadhandle);
|
|
throw std::logic_error("simplethread: ResumeThread() failed unexpectedly");
|
|
}
|
|
try
|
|
{
|
|
startsignal.wait(); // wait until functor has been copied
|
|
}
|
|
catch (...)
|
|
{
|
|
::TerminateThread(threadhandle, 0);
|
|
::CloseHandle(threadhandle);
|
|
throw;
|
|
}
|
|
}
|
|
// check if the thread is still alive and without error
|
|
void check()
|
|
{
|
|
CAutoLock lock(*this);
|
|
// pass on a pending exception
|
|
if (exceptionptr)
|
|
throw * exceptionptr.get();
|
|
// the thread going away without error is also unexpected at this point
|
|
if (wait(0)) // (0 means don't block, so OK to call inside lock)
|
|
throw std::runtime_error("check: thread terminated unexpectedly");
|
|
}
|
|
bool wait(DWORD dwMilliseconds = INFINITE)
|
|
{
|
|
DWORD rc = ::WaitForSingleObject(threadhandle, dwMilliseconds);
|
|
if (rc == WAIT_TIMEOUT)
|
|
return false;
|
|
else if (rc == WAIT_OBJECT_0)
|
|
return true;
|
|
else
|
|
throw std::runtime_error("wait: WaitForSingleObject() failed unexpectedly");
|
|
}
|
|
// thread itself can set the failure condition, e.g. before it signals some other thread to pick it up
|
|
void fail(const std::exception &e)
|
|
{
|
|
// exception: remember it --this will remove the type info :(
|
|
CAutoLock lock(*this);
|
|
try // copy the exception--this may fail if we are out of memory
|
|
{
|
|
exceptionptr.reset(new std::runtime_error(e.what()));
|
|
}
|
|
catch (...) // failed to alloc: fall back to bad_alloc, which is most likely the cause in such situation
|
|
{
|
|
exceptionptr = badallocexceptionptr;
|
|
}
|
|
}
|
|
// void join()
|
|
// {
|
|
// check();
|
|
// wait();
|
|
// check_for_exception(); // (check() not sufficient because it would fail since thread is gone)
|
|
// }
|
|
~simplethread() throw()
|
|
{
|
|
// wait until it shuts down
|
|
try
|
|
{
|
|
wait();
|
|
}
|
|
catch (...)
|
|
{
|
|
::TerminateThread(threadhandle, 0);
|
|
}
|
|
// close the handle
|
|
::CloseHandle(threadhandle);
|
|
}
|
|
};
|
|
};
|
|
};
|