{"id":692,"date":"2018-03-30T10:28:53","date_gmt":"2018-03-30T10:28:53","guid":{"rendered":"http:\/\/cppdepend.com\/blog\/?p=692"},"modified":"2023-05-31T15:36:27","modified_gmt":"2023-05-31T15:36:27","slug":"microsoft-crt-deep-dive-into-task-scheduler","status":"publish","type":"post","link":"https:\/\/cppdepend.com\/blog\/microsoft-crt-deep-dive-into-task-scheduler\/","title":{"rendered":"Microsoft CRT: Deep Dive into Task Scheduler"},"content":{"rendered":"<p>The Task Scheduler schedules and coordinates tasks at run time. A task is a unit of work that performs a specific job. The Task Scheduler manages the details that are related to efficiently scheduling tasks on computers that have multiple computing resources.<\/p>\n<p>Windows OS provides a preemptive kernel-mode scheduler, it&#8217;s a round-robin, priority-based mechanism that gives every task exclusive access to a computing resource for a given time period, and then switches to another task.Although this mechanism provides fairness (every thread makes forward progress), it comes at some cost of efficiency.For example, many computation-intensive algorithms do not require fairness. Instead, it is important that related tasks finish in the least overall time. Cooperative scheduling enables an application to more efficiently schedule work.<!--more--><\/p>\n<p>Cooperative scheduling is a mechanism that gives every task exclusive access to a computing resource until the task finishes or until the task yields its access to the resource.<\/p>\n<p>The user-mode cooperative scheduler enables application code to make its own scheduling decisions. Because cooperative scheduling enables many scheduling decisions to be made by the application, it reduces much of the overhead that is associated with kernel-mode synchronization.<\/p>\n<p>The Concurrency Runtime uses cooperative scheduling together with the preemptive scheduler of the operating system to achieve maximum usage of processing resources.<\/p>\n<p><strong>Scheduler Design<\/strong><\/p>\n<p>The concurrency runtime provides the interface\u00a0Scheduler\u00a0to implement a specific scheduler adapted to application needs.<\/p>\n<p>Let&#8217;s discover classes implementing this interface, for that we can execute the following CQL request:<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI0Ft15_rZI\/AAAAAAAAAhk\/r8flyQtMUAQ\/s1600\/crt1.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516071403721305490\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI0Ft15_rZI\/AAAAAAAAAhk\/r8flyQtMUAQ\/s800\/crt1.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>The concurrency runtime provides two implementations of scheduler,\u00a0ThreadScheduler\u00a0and\u00a0UMSThreadScheduler.<\/p>\n<p>The scheduler as shown by the following dependency graph, reference many abstrac classes to acheive its goal:<\/p>\n<div><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI06ZdC7BeI\/AAAAAAAAAi8\/aQPogg1Tt-M\/s1600\/crt10.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516129327566751202\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI06ZdC7BeI\/AAAAAAAAAi8\/aQPogg1Tt-M\/s800\/crt10.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>Let&#8217;s discover the role of each abstract class used by the scheduler, for that we will discuss its responsibilities.<\/p>\n<p>There are three major responsibilities assigned to the Task Scheduler:<\/p>\n<p><strong>1) Getting Resources(Processors, Cores, Memory):<\/strong><\/p>\n<p>When the scheduler is created it ask for resources from runtime resource manager as explained in this\u00a0<a href=\"http:\/\/www.drdobbs.com\/windows\/227300348\">article<\/a>.<\/p>\n<p>The scheduler communicates with the resource manager using\u00a0IResourceManager,\u00a0ISchedulerProxy\u00a0and\u00a0IScheduler\u00a0interfaces.<br \/>\nWhen creating the scheduler we can specify its policy.<\/p>\n<p>The Concurrency::PolicyElementKey enumeration defines the policy keys that are associated with the Task Scheduler.<\/p>\n<div><a href=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI0PXpIjfBI\/AAAAAAAAAh0\/ZU4EhJrXoyI\/s1600\/crt8.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516082017451867154\" src=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI0PXpIjfBI\/AAAAAAAAAh0\/ZU4EhJrXoyI\/s800\/crt8.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>Here&#8217;s an\u00a0<a href=\"http:\/\/help.outlook.com\/en-us\/140\/ff829269.aspx\">article\u00a0<\/a>explaining the utility of each policy key, and the default value of each key.<\/p>\n<p>Here&#8217;s a dependency graph to show what happens when we create a scheduler:<\/p>\n<p><a href=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI0IflRtjdI\/AAAAAAAAAhs\/ePregKv2QSw\/s1600\/crt2.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516074457274093010\" src=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI0IflRtjdI\/AAAAAAAAAhs\/ePregKv2QSw\/s800\/crt2.PNG\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>The concurrency runtime create a default scheduler if no scheduler exists by invocking GetDefaultScheduler method, and a default policy is used.<br \/>\nThe Task Scheduler enables applications to use one or more\u00a0<span class=\"parameter\">scheduler instances<\/span>\u00a0to schedule work, and an application can invoke Scheduler::Create to add another scheduler that uses a specific policy.<\/p>\n<p>The following collaborations between the scheduler and resource manager shows the role of each interface concerned by the allocation.<\/p>\n<p>&#8211; Ask for resource allocation:<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI6QzdF4iSI\/AAAAAAAAAjM\/djSB5C-Piok\/s1600\/crt13.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516505807232469282\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI6QzdF4iSI\/AAAAAAAAAjM\/djSB5C-Piok\/s800\/crt13.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>&#8211; Getting resources from resource manager:<br \/>\n<a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI6RCXUc5uI\/AAAAAAAAAjU\/N_PkzZHkpFM\/s1600\/crt14.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516506063380997858\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI6RCXUc5uI\/AAAAAAAAAjU\/N_PkzZHkpFM\/s800\/crt14.PNG\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p><strong>2) Manage the task queues:<\/strong><\/p>\n<p>When Schedeluer is created, tasks could be assigned to it to be executed, the Scheduler store tasks into queues.to enforce the cohesion of classes, the queues are not managed directly by the ThreadScheduler class but by ScheduleGroupBase class.<\/p>\n<p>A schedule group affinitizes, or groups, related tasks together. Every scheduler has one or more schedule groups. Use schedule groups when you require a high degree of locality among tasks, for example, when a group of related tasks benefit from executing on the same processor node.<\/p>\n<p>As shown in the following graph, the runtime provides two kind of ScheduleGroup:\u00a0FairScheduleGroup\u00a0and\u00a0CacheLocalScheduleGroup, choosing between thoses two groups as it will be explained later impact the algorithm of choosing the next task to execute by the scheduler.<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI0RfUElPrI\/AAAAAAAAAh8\/Tzs3UJjvW2w\/s1600\/crt4.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516084348260269746\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI0RfUElPrI\/AAAAAAAAAh8\/Tzs3UJjvW2w\/s800\/crt4.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>Every\u00a0<strong>Scheduler<\/strong>\u00a0has a default schedule group for every scheduling node.The runtime creates one scheduling node for every processor package or Non-Uniform Memory Architecture (NUMA) node. If you do not explicitly associate a task with a schedule group, the scheduler chooses which group to add the task to.<\/p>\n<p>As shown in the following dependency graph , The schedulingRing is responsable of managing Scheduler Groups, it contains a list of groups and create them.<\/p>\n<p><a href=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI0ob9EuuWI\/AAAAAAAAAiU\/1eirhK_4yeU\/s1600\/crt5.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516109579314706786\" src=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI0ob9EuuWI\/AAAAAAAAAiU\/1eirhK_4yeU\/s800\/crt5.PNG\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>The Schedule group contains three kind of queues:<\/p>\n<p><strong>1) FIFO Queue<\/strong><\/p>\n<p>This queue contains lightweight tasks, a lightweight task resembles the function that you provide to the Windows API\u00a0CreateThread\u00a0function. Therefore, lightweight tasks are useful when you adapt existing code to use the scheduling functionality of the Concurrency Runtime.<\/p>\n<p>A lighweight task is represented by\u00a0RealizedChore\u00a0class, and the FIFO queue of the schedule group is represented by the field m_realizedChores.<\/p>\n<p>Let&#8217;s search for methods using directly this queue:<\/p>\n<div><a href=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI0oV6JMSQI\/AAAAAAAAAiM\/dgLEJSoUV9o\/s1600\/crt6.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516109475448899842\" src=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI0oV6JMSQI\/AAAAAAAAAiM\/dgLEJSoUV9o\/s800\/crt6.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>So we can add a lightweight task to the group by invoking ScheduleGroupBase::ScheduleTask or Scheduler::ScheduleTask.<\/p>\n<p><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6bGnXVKCI\/AAAAAAAAAjc\/ajCN5K4enI4\/s1600\/crt12.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516517131523794978\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6bGnXVKCI\/AAAAAAAAAjc\/ajCN5K4enI4\/s800\/crt12.PNG\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>Here&#8217;s an interesting\u00a0<a href=\"http:\/\/help.outlook.com\/en-us\/140\/ff829270.aspx\">article<\/a>\u00a0talking about lighweight tasks.<\/p>\n<p><strong>2)Work Stealing queue:<\/strong><\/p>\n<p>There&#8217;s only one FIFO queue associated to the schedule group, but the schedule group reference a list of work sealing queues, for each worker thread there&#8217;s a local queue associated with it.<\/p>\n<p>A thread that is attached to a scheduler is known as an\u00a0<span class=\"parameter\">execution context<\/span>, or just\u00a0<span class=\"parameter\">context<\/span>, so actually this local queue is associated to\u00a0Context\u00a0class.<\/p>\n<p>The\u00a0Context\u00a0class provides a programming abstraction for an execution context and offer the ability to cooperatively block, unblock, and yield the current context.<\/p>\n<p>And to be sure that only\u00a0the Context create this kind of queue, let&#8217;s search of the methods accessing directly to m_workQueues field.<\/p>\n<div><a href=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI6izSohBZI\/AAAAAAAAAjk\/HE-_ME3wtiI\/s1600\/crt15.PNG\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516525595634238866\" src=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI6izSohBZI\/AAAAAAAAAjk\/HE-_ME3wtiI\/s800\/crt15.PNG\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>&nbsp;<\/p>\n<p>The context is responsable of creating this queue, and for each context there&#8217;s a local work stealing queue associated to it.<\/p>\n<p>To illustrate the behavior of the work-stealing algorithm, let&#8217;s supposethat we have two worker thread allocated to the scheduler.<\/p>\n<p>As explained before for each worker thread there&#8217;s a local queue associated with it.<\/p>\n<div><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6xp2wUIbI\/AAAAAAAAAjs\/gTuiqgdiS9Q\/s1600\/crt16.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516541926206349746\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6xp2wUIbI\/AAAAAAAAAjs\/gTuiqgdiS9Q\/s800\/crt16.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p><span class=\"longtext\"><span lang=\"EN-US\">Three tasks in the queue Worker Thread 1, tasks 3 and 4 are waiting to be executed while the 5 is running.<\/span><\/span><\/p>\n<div><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6xwqoxkBI\/AAAAAAAAAj0\/1hT0R5wC3bg\/s1600\/crt17.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516542043212582930\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI6xwqoxkBI\/AAAAAAAAAj0\/1hT0R5wC3bg\/s800\/crt17.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p><span class=\"longtext\"><span lang=\"EN-US\">The Dispatch method finds that there is nothing in the queue, so the task 3 is moved, or &#8220;stolen&#8221; from its original queue, to be distributed\u00a0the worker thread available.<\/span><\/span><\/p>\n<div><a href=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI6x1fRXxDI\/AAAAAAAAAj8\/YLWEGMElnPg\/s1600\/crt18.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516542126060979250\" src=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI6x1fRXxDI\/AAAAAAAAAj8\/YLWEGMElnPg\/s800\/crt18.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>How we can create a task managed by work stealing queue? for that let&#8217;s search for methods indirectly invocking the CreateWorkQueue method.<\/p>\n<p>As shown in this dependency graph, this kind of task could be created by using task_group class.<\/p>\n<p><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI63TDKzvUI\/AAAAAAAAAkE\/wTXtrdZVL3M\/s1600\/crt19.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516548131471473986\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI63TDKzvUI\/AAAAAAAAAkE\/wTXtrdZVL3M\/s800\/crt19.png\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>Using task_group to add new task is more better than using Sheduler::ScheduleTask to create a lighweight task, indeed the work stealing algorithm optimize better the using of virtual processors allocated to the scheduler.<\/p>\n<p>However, ScheduleTask could be better to migrate easilly from the existing code using CreateThread API.<\/p>\n<p>3)Unblocqued Context queue:<\/p>\n<p>The\u00a0<strong>Context<\/strong>\u00a0class lets you block or yield the current execution context. Blocking or yielding is useful when the current context cannot continue because a resource is not available.<br \/>\nThe Context::Block method blocks the current context. A context that is blocked yields its processing resources so that the runtime can perform other tasks. The Context::Unblock method unblocks a blocked context.<\/p>\n<p>When a context is unblocked and it&#8217;s available to be executed , it&#8217;s add to runnable context queue, this queue is represented by the field m_runnableContexts.<\/p>\n<p>Here&#8217;s a dependency graph showing some cases where the context is added to runnable queue:<\/p>\n<div><a href=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI9oHgdfd8I\/AAAAAAAAAlc\/tesEdzwNEfg\/s1600\/crt29.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516742546734151618\" src=\"http:\/\/3.bp.blogspot.com\/_tWDA5bNBNHI\/TI9oHgdfd8I\/AAAAAAAAAlc\/tesEdzwNEfg\/s800\/crt29.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>So the context is added to the queue when it&#8217;s unblocked or a virtual processor is retired from the scheduler.<br \/>\n<strong>3)Dispatching Tasks:<\/strong><\/p>\n<p>The scheduler try to search for work to execute, a work could be:<\/p>\n<ul>\n<li>\u00a0Unblocked context.<\/li>\n<li>\u00a0Lightweight task.<\/li>\n<li>\u00a0Task in work stealing queues.<\/li>\n<\/ul>\n<p>And as explained before all these works are stored in queues managed by Schedule groups, and each group is managed by a scheduling ring.<\/p>\n<p>When a virtual processor is allocated to the scheduler a ThreadProxy class is created and associated to this processor. and after the creation, the Dispatch method of the ThreadProxy is invoked.<br \/>\nAs shown in the following dependency graph and as explained before the concurrency runtime use abstract classes to enforces low coupling, and the real dispatch invoked depends of the implementation choosed by the runtime, this choice is given by the scheduler policy.<\/p>\n<div><a href=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI-Fb8TMddI\/AAAAAAAAAls\/cllj62tPCoI\/s1600\/crt31.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516774783641744850\" src=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI-Fb8TMddI\/AAAAAAAAAls\/cllj62tPCoI\/s800\/crt31.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>The concrete implementation of Dispatch invoke Dispatch Method of the execution context.<\/p>\n<p>Here&#8217;s a dependec graph showing methods invocked by a concrete implementation of Context::Dispatch method:<a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI7BhPSDyoI\/AAAAAAAAAkM\/AII7E3j1UnE\/s1600\/crt20.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516559370357557890\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI7BhPSDyoI\/AAAAAAAAAkM\/AII7E3j1UnE\/s800\/crt20.png\" alt=\"\" border=\"0\" \/><\/a><br \/>\nSo the algorithm of searching the next work to execute is implemented by\u00a0WorkSearchContext\u00a0class.<\/p>\n<p>Let&#8217;s discover all classes used directly by WorkSearchContext to acheive its responsability:<\/p>\n<div><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9VQy23ljI\/AAAAAAAAAks\/hkyBxLkaBcU\/s1600\/crt23.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516721815570322994\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9VQy23ljI\/AAAAAAAAAks\/hkyBxLkaBcU\/s400\/crt23.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>The responsability of WorkSearchContext is to give us a\u00a0WorkItem\u00a0to execute, it could be InternalContextBase, RealizedChore or _UnrealizedChore.<\/p>\n<p>To understand better the collaboration between these classes , let&#8217;s search for methods used directly by WorkSearchContext:<\/p>\n<div><a href=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI9twLk39FI\/AAAAAAAAAlk\/8bNW6zrkalA\/s1600\/crt30.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516748743060747346\" src=\"http:\/\/1.bp.blogspot.com\/_tWDA5bNBNHI\/TI9twLk39FI\/AAAAAAAAAlk\/8bNW6zrkalA\/s800\/crt30.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>So the WorkSearchContext iterate on\u00a0SchedulingRing\u00a0and\u00a0SheduleGroup\u00a0classes by using SchedulerBase methods.<\/p>\n<p>And for each ScheduleBase we search for RunnableContext, realizedChore or UnrealizedChore.<\/p>\n<p>The WorkSearchContext class is creaed by the\u00a0VirtualProcessor\u00a0class, and as shown in the following dependency graph, the algorithm used is specified when the VirtualProcessor is initialized, and for that, it ask the Scheduler for the\u00a0SchedulingProtocol\u00a0which describe the scheduling algorithm that will be utilized for the scheduler.<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9QFHgk0_I\/AAAAAAAAAkU\/bsazG-6cLEk\/s1600\/crt21.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516716117397394418\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9QFHgk0_I\/AAAAAAAAAkU\/bsazG-6cLEk\/s800\/crt21.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>The WorkSearchContext will be notified by the algorithm to use by passing it a value from Algorithm enum.<\/p>\n<div><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9lIhRA27I\/AAAAAAAAAlU\/ZQd-AiqOkrQ\/s1600\/crt28.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516739265595235250\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9lIhRA27I\/AAAAAAAAAlU\/ZQd-AiqOkrQ\/s800\/crt28.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>So two algorithmes are implemented by this class to find a work:<\/p>\n<p>&#8211; <strong>Cache Local algorithm:<\/strong><\/p>\n<p>This algorithm will look for runnables context within the current schedule group, then realized chores then unrealized chores, if there&#8217;s no more work in the curent schedule group it look for the next group in the same shedule ring, and when it finish all works in the current schedule ring it look in the next schedule ring.<\/p>\n<p>So the scheduler prefers to continue to work on tasks within the current schedule group before moving to another schedule group.<\/p>\n<p>This algorithm is implemented by WorkSearchContext::SearchCacheLocal method, and as show by this dependency graph, this method search invoke other methods to search for runnable contexts, RealizedChore or _UnrealizedChore.<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9jvQ8T0FI\/AAAAAAAAAlE\/17E0hH2HvSI\/s1600\/crt26.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516737732205072466\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9jvQ8T0FI\/AAAAAAAAAlE\/17E0hH2HvSI\/s800\/crt26.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n<p>Another specificity of this algorithm is that the unblocked contexts are cached per virtual-processor and are typically scheduled in a last-in first-out (LIFO) fashion by the virtual processor which unblocked them.<\/p>\n<p>And to verify this behavior, here&#8217;s a dependency graph of methods invocked when searching for runnable context:<\/p>\n<p><a href=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9etGE6HYI\/AAAAAAAAAk0\/hii5m-7j_84\/s1600\/crt24.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516732197370469762\" src=\"http:\/\/4.bp.blogspot.com\/_tWDA5bNBNHI\/TI9etGE6HYI\/AAAAAAAAAk0\/hii5m-7j_84\/s800\/crt24.png\" alt=\"\" border=\"0\" \/><\/a><\/p>\n<p>This algorithm is the default algorithm choosen by the scheduler if no one is specified.<\/p>\n<p>&#8211; <strong>Fair algorithm:<\/strong><\/p>\n<p>In this case, the scheduler prefers to round-robin through schedule groups after executing each task. Unblocked contexts are typically scheduled in a first-in-first-out (FIFO) fashion. Virtual processors do not cache unblocked contexts.<\/p>\n<p>This algorithm is implemented by WorkSearchContext::SearchFair method, and as show by this dependency graph, this method search invoke other methods to search for runnable contexts, RealizedChore or _UnrealizedChore.<\/p>\n<div><a href=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9j9twevoI\/AAAAAAAAAlM\/Hme8r2mroKk\/s1600\/crt27.png\"><img decoding=\"async\" id=\"BLOGGER_PHOTO_ID_5516737980458253954\" src=\"http:\/\/2.bp.blogspot.com\/_tWDA5bNBNHI\/TI9j9twevoI\/AAAAAAAAAlM\/Hme8r2mroKk\/s800\/crt27.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Task Scheduler schedules and coordinates tasks at run time. A task is a unit of work that performs a specific job. The Task Scheduler manages the details that are related to efficiently scheduling tasks on computers that have multiple computing resources. Windows OS provides a preemptive kernel-mode scheduler, it&#8217;s a round-robin, priority-based mechanism that &hellip; <a href=\"https:\/\/cppdepend.com\/blog\/microsoft-crt-deep-dive-into-task-scheduler\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Microsoft CRT: Deep Dive into Task Scheduler&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[61],"class_list":["post-692","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-crt"],"_links":{"self":[{"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/posts\/692","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/comments?post=692"}],"version-history":[{"count":5,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/posts\/692\/revisions"}],"predecessor-version":[{"id":1471,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/posts\/692\/revisions\/1471"}],"wp:attachment":[{"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/media?parent=692"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/categories?post=692"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cppdepend.com\/blog\/wp-json\/wp\/v2\/tags?post=692"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}