I have 1 Question put by customer peer..
He is writing his own program for task-switching of threads, infact he has already coded completely using Win32 API's, like Set/GetContext, DumpStack, Suspend/ResumeThread,
Currently, it crashes under certain situations only, probably in places where register is not getting proper value when stack context is dumped back for execution..
At this point, he does not want to go into details of his self-written code, just want a high-level answer (may be like a sequence/event flow, or process flow, or probably few
assembly tricks may be)..
Suppose I have some background thread, Tb, doing monitoring threads work, for simplicity, 1 thread T1 ..
T1 is doing work 1-2-3-4-5-6-7-8 in sequence..
At some point T1 is executing work 3 .. Tb needs T1 to suspend work 3, instead carry out 8, and after finishing jump back to original work 3 ..
1. Tb suspends T1, so now work 3 is suspended..
2. Default I think is __fastcall, so Tb will save callee function registers, do stack cleanup etc.. instead of self-cleanup by callee..
--> plz correct if its wrong..
3. Tb sets/saves context , and T1 has to do work 8..
-> How will Tb setup T1 to do work 8, and get the saved context/registers back and jump T1 to work 3 to finish the further works, gracefully..
In 3., there are lot of problems that would come, related to Suspending/Resuming Thread T1, context-restores, so that if any of original register values are not set properly,
runtime crash will occur ..
putting another way, he wants to know how 1 thread can read the register values (say rip, rax, rcx) of another thread, and set it back to proper values.. i.e, doing save/restore explicitly by some means?
If user-code is needed to examine, I can zip the source and attach here for analysis..
It appears what you are describing is either:
Atime slicing of a user thread pool that is larger than available hardware threads.
Or, variable thread prioritization of a user thread pool that is larger than available hardware threads.
In these types of systems any thread can be preempted at any point in the code subject to interrupts. Typically an operating system performs this type of context switch.
An alternate approach is for the user application to not oversubscribe the compute boundsoftware threads above the number of hardware threads with additional threads allocated for running blocking (e.g. I/O) functions. This approach is called Task Model. In task model, Task context switches are not preemptive, tasks run to completion (subject to O/S preemption but not user application monitoring preemption). In task model, tasks are created, additional threads above those at init time are not created. The pool of created threads extract the next task from either a prioritized queue or non-prioritized queue (e.g. FIFO, LIFO, etc...).
In task model, should your design require oversubscription, then your task code can at appropriate positions issue something like a sleep(0) or SwitchToThread(), or FunctionToLowerPriority(), etc... which causes the operating system to potentially preempt the thread running the current task. Note, SwitchToThread() is not necessarily implemented in the way you expect from the name of the function nor from the description in the API documentation. Experiment using both sleep(0) and SwitchToThread() under varying load conditions.
The benifits of task model are: elimination of (possibly high priority) monitoring thread, and minimizing register context switching and reducing stack load (should you implement via synthesized function call).
A hybrid approach is to sprinkle through your interruptible tasks explicit switch via functor, one per task, possibly in thread local storage, or in array of functors. A functor is afunction pointer. Actual implementation is up to you.Example might be, when the functor is NULL your task will not call the function located at the address contained in the functor. Whenyourdivertabletask is running an functor is non-NULL, divert-able task executes function (now sub task) and firststatementof sub task NULL's functor indicatingtask started (orNULL's functor at end indicating task complete, or writes 1 into functor indicating task started andNULL at task end indicating task complete).
The polling of the functor is controlled by you and has negligible overhead. This comes at the expense of some latency.