dougbinks / enkiTS

A permissively licensed C and C++ Task Scheduler for creating parallel programs. Requires C++11 support.
zlib License
1.74k stars 145 forks source link

Suggestions for propagating user data into tasks? Client+Server in same exe #104

Closed BrodyHiggerson closed 10 months ago

BrodyHiggerson commented 10 months ago

Hi, I'm evaluating converting over from a fiber-based library to enkiTS, and the API all looks good and I'm looking forward to using it.

One specific implementation detail I have at the moment is the ability to trace from the current fiber back to the "main" fiber that created it, and then read some user data I stashed somewhere associated with said main fiber. E.g. the caller of IsClient() or IsServer() effectively says "which main fiber lead to my creation?" and then uses that ID to read a block of user data containing things like server vs client. This allows for e.g. client and server in the same exe, with shared code that always knows which one it was created from. It's not locked to any specific thread, more of a propagation - if the client main fiber creates a task, that task's fiber knows where it came from, and if that task creates a task, it passes that same info on, and so on and so forth.

I'm just bouncing ideas around but trying to think of how I could capture the same ability, allowing any code to know which master/main thread it originated from regardless of whether it's actively inside an enki task or not. I imagine thread_local data could help, but the enki tasks could end up on any thread. It's almost like I want to intercept task creation on the original creating thread, read some thread_local ID to use for look-up later, and then pass it through to the task, which upon start of execution on whichever thread it's on sets its own thread_local to match, effectively propagating the ID or data. And when tasks finish, we erase that ID.

Are there any mechanisms that could help, or alternatives you might have seen?

dougbinks commented 10 months ago

For enkiTS tasks you could create a base task class you derive from and have that store GetThreadNum(), which will be valid for any enkiTS task or external registered threads. Since you can only launch tasks from enkITS task or external registered threads this should be sufficient.

BrodyHiggerson commented 10 months ago

Okay cool, thanks. So it sounds like the following;

I wish I could react to a task being about to be executed, so users inheriting and making their own tasks don't have to remember to call down the tree to get to that base, i.e. some OnTaskPreExecute(). But I get it might not be elegant and may complicate the API. Might do something like that in a fork, we'll see!

dougbinks commented 10 months ago

One option instead of using thread local storage is to use an array the size of GetNumTaskThreads() and use GetThreadNum() to index into it. This is potentially faster (depending on system).

It would seem to me that your users only need to inherit from the right class, and override a new ExecuteRangeNew() virtual function. You could derive with private access control and implement your own interface if you want. The base classes ExecuteRange() would then perform the setup required and call the ExecuteRangeNew().