Tracking thread creation points for debugging

I fell in love with lambdas, and some time ago I wrote a simple shell that takes a lambda and runs it in a new thread.

// // Starts a task on a separate thread, when passed a lambda expression // template<typename T> smart_ptrs::w32handle StartTask(T f) { // Make a copy of the task on the heap T* pTask = new T(f); // Create a new thread to service the task smart_ptrs::w32handle hThread(::CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)& detail::start_task_proc<T>, (LPVOID) pTask, NULL, NULL)); // If the caller ignores this rc, the thread handle will simply close and the // thread will continue in fire-and-forget fashion. return hThread; } 

NOTE : Yes, I know that this really does not need a template and can happily use std::function . I placed it this way because it corresponds to the more complex (asynchronous) versions that I have, which should be templates.

The end result is a function that is very easy to use in parallel algorithms, etc. However, a problem arises if you start to widely use such a function. Since the created threads are created from the same fairly general function, it becomes difficult to determine in which code they were running. You can usually work out from the context of what they are doing, but this is not as simple as before when an explicit stream function is used. Does anyone have a good method of tagging such threads to make it easier to debug?

+7
source share
1 answer

As far as I can tell from the code, you will have threads with start_task_proc somewhere in your stack that calls their function object. You can change this function by pointing to the "task information" structure, and not to the bare function object. You can use any information that you like in this information object, for example. line numbers and file names in which you created the task:

 template <class T> struct TaksInfo { T func; unsigned line; char const* file; TaskInfo(T&& t, unsigned l, char const* f) : func(std::move(t), line(l), file(f) {} }; template<typename T> smart_ptrs::w32handle StartTask(T f, unsigned line, char const* file) { // Make a copy of the task on the heap auto pTask = new TaskInfo<T>(f, line, file); // Create a new thread to service the task smart_ptrs::w32handle hThread(::CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)& detail::start_task_proc<T>, (LPVOID) pTask, NULL, NULL)); // If the caller ignores this rc, the thread handle will simply close and the // thread will continue in fire-and-forget fashion. return hThread; } #define START_TASK(f) StartTask(f, __LINE__, __FILE__) template <class T> DWORD start_task_proc(LPVOID lpParameter) { auto pTask = (TaskInfo<T>*) lpParameter; return pTask->func(); } //use: int main() { auto threadHandle = START_TASK(([]() -> DWORD { std::cout << "foo\n"; return 42;} )); } 

If you are now checking pTask in start_task_proc , you can see file and line that can tell you where the task was run.
Of course, you could avoid the TaskInfo structure and make the information just a parameter to the start_task_proc template:

 template <class T, unsigned Line, char const* File> DWORD start_task_proc(LPVOID lpParameter) { /* as you have it */) template<unsigned Line, char const* File, typename T> //use T last fur type deduction smart_ptrs::w32handle StartTask(T f) { // Make a copy of the task on the heap auto pTask = new T(std::move(f)); // Create a new thread to service the task smart_ptrs::w32handle hThread(::CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)& detail::start_task_proc<T, Line, File>, //!!! (LPVOID) pTask, NULL, NULL)); // If the caller ignores this rc, the thread handle will simply close and the // thread will continue in fire-and-forget fashion. return hThread; } #define START_TASK(f) StartTask<__LINE__, __FILE__>(f) 
+3
source

All Articles