[dxvk] Rewrite thread wrapper

Addresses some issues raised in #3378.
This commit is contained in:
Philip Rebohle 2023-04-25 11:49:17 +02:00
parent b44c5bbd18
commit 5c8ed491ab
2 changed files with 132 additions and 89 deletions

View File

@ -1,10 +1,82 @@
#include <atomic>
#include "thread.h" #include "thread.h"
#include "util_likely.h" #include "util_likely.h"
#include <atomic>
#ifdef _WIN32 #ifdef _WIN32
namespace dxvk {
thread::thread(ThreadProc&& proc)
: m_data(new ThreadData(std::move(proc))) {
m_data->handle = ::CreateThread(nullptr, 0x100000,
thread::threadProc, m_data, STACK_SIZE_PARAM_IS_A_RESERVATION,
&m_data->id);
if (!m_data->handle) {
delete m_data;
throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again), "Failed to create thread");
}
}
thread::~thread() {
if (joinable())
std::terminate();
}
void thread::join() {
if (!joinable())
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Thread not joinable");
if (get_id() == this_thread::get_id())
throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur), "Cannot join current thread");
if(::WaitForSingleObjectEx(m_data->handle, INFINITE, FALSE) == WAIT_FAILED)
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "Joining thread failed");
detach();
}
void thread::set_priority(ThreadPriority priority) {
int32_t value;
switch (priority) {
default:
case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break;
case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break;
}
if (m_data)
::SetThreadPriority(m_data->handle, int32_t(value));
}
uint32_t thread::hardware_concurrency() {
SYSTEM_INFO info = { };
::GetSystemInfo(&info);
return info.dwNumberOfProcessors;
}
DWORD WINAPI thread::threadProc(void* arg) {
auto data = reinterpret_cast<ThreadData*>(arg);
DWORD exitCode = 0;
try {
data->proc();
} catch (...) {
exitCode = 1;
}
data->decRef();
return exitCode;
}
}
namespace dxvk::this_thread { namespace dxvk::this_thread {
bool isInModuleDetachment() { bool isInModuleDetachment() {

View File

@ -5,6 +5,7 @@
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <utility>
#include "util_error.h" #include "util_error.h"
@ -24,126 +25,96 @@ namespace dxvk {
}; };
#ifdef _WIN32 #ifdef _WIN32
using ThreadProc = std::function<void()>;
/** /**
* \brief Thread helper class * \brief Thread object
*
* This is needed mostly for winelib builds. Wine needs to setup each thread that
* calls Windows APIs. It means that in winelib builds, we can't let standard C++
* library create threads and need to use Wine for that instead. We use a thin wrapper
* around Windows thread functions so that the rest of code just has to use
* dxvk::thread class instead of std::thread.
*/ */
class ThreadFn : public RcObject { struct ThreadData {
using Proc = std::function<void()>; ThreadData(ThreadProc&& proc_)
public: : proc(std::move(proc_)) { }
ThreadFn(Proc&& proc) ~ThreadData() {
: m_proc(std::move(proc)) { if (handle)
// Reference for the thread function CloseHandle(handle);
this->incRef();
m_handle = ::CreateThread(nullptr, 0x100000,
ThreadFn::threadProc, this, STACK_SIZE_PARAM_IS_A_RESERVATION,
nullptr);
if (m_handle == nullptr)
throw DxvkError("Failed to create thread");
} }
~ThreadFn() { HANDLE handle = nullptr;
if (this->joinable()) DWORD id = 0;
std::terminate(); std::atomic<uint32_t> refs = { 2u };
ThreadProc proc;
void decRef() {
if (refs.fetch_sub(1, std::memory_order_release) == 1)
delete this;
} }
void detach() {
::CloseHandle(m_handle);
m_handle = nullptr;
}
void join() {
if(::WaitForSingleObjectEx(m_handle, INFINITE, FALSE) == WAIT_FAILED)
throw DxvkError("Failed to join thread");
this->detach();
}
bool joinable() const {
return m_handle != nullptr;
}
void set_priority(ThreadPriority priority) {
int32_t value;
switch (priority) {
default:
case ThreadPriority::Normal: value = THREAD_PRIORITY_NORMAL; break;
case ThreadPriority::Lowest: value = THREAD_PRIORITY_LOWEST; break;
}
::SetThreadPriority(m_handle, int32_t(value));
}
private:
Proc m_proc;
HANDLE m_handle;
static DWORD WINAPI threadProc(void *arg) {
auto thread = reinterpret_cast<ThreadFn*>(arg);
thread->m_proc();
thread->decRef();
return 0;
}
}; };
/** /**
* \brief RAII thread wrapper * \brief Thread wrapper
* *
* Wrapper for \c ThreadFn that can be used * Drop-in replacement for std::thread
* as a drop-in replacement for \c std::thread. * using plain win32 threads.
*/ */
class thread { class thread {
public: public:
using id = uint32_t;
using native_handle_type = HANDLE;
thread() { } thread() { }
explicit thread(std::function<void()>&& func) explicit thread(ThreadProc&& proc);
: m_thread(new ThreadFn(std::move(func))) { }
~thread();
thread(thread&& other) thread(thread&& other)
: m_thread(std::move(other.m_thread)) { } : m_data(std::exchange(other.m_data, nullptr)) { }
thread& operator = (thread&& other) { thread& operator = (thread&& other) {
m_thread = std::move(other.m_thread); if (m_data)
m_data->decRef();
m_data = std::exchange(other.m_data, nullptr);
return *this; return *this;
} }
void detach() { void detach() {
m_thread->detach(); m_data->decRef();
} m_data = nullptr;
void join() {
m_thread->join();
} }
bool joinable() const { bool joinable() const {
return m_thread != nullptr return m_data != nullptr;
&& m_thread->joinable();
} }
void set_priority(ThreadPriority priority) { id get_id() const {
m_thread->set_priority(priority); return joinable() ? m_data->id : id();
} }
static uint32_t hardware_concurrency() { native_handle_type native_handle() const {
SYSTEM_INFO info = { }; return joinable() ? m_data->handle : native_handle_type();
::GetSystemInfo(&info);
return info.dwNumberOfProcessors;
} }
void swap(thread& other) {
std::swap(m_data, other.m_data);
}
void join();
void set_priority(ThreadPriority priority);
static uint32_t hardware_concurrency();
private: private:
Rc<ThreadFn> m_thread; ThreadData* m_data = nullptr;
static DWORD WINAPI threadProc(void* arg);
}; };
@ -153,8 +124,8 @@ namespace dxvk {
SwitchToThread(); SwitchToThread();
} }
inline uint32_t get_id() { inline thread::id get_id() {
return uint32_t(GetCurrentThreadId()); return thread::id(GetCurrentThreadId());
} }
bool isInModuleDetachment(); bool isInModuleDetachment();