Condividi tramite


Procedura: utilizzare i gruppi di pianificazione per influenzare l'ordine di esecuzione

Nel runtime di concorrenza l'ordine in cui vengono pianificate le attività non è deterministico. Tuttavia, è possibile utilizzare i criteri di pianificazione per influenzare l'ordine di esecuzione delle attività. In questo argomento viene illustrato come utilizzare i gruppi di pianificazione con i criteri dell'utilità di pianificazione Concurrency::SchedulingProtocol per influenzare l'ordine di esecuzione delle attività.

Nell'esempio viene eseguito due volte un set di attività, ognuno con criteri di pianificazione diversi. Entrambi i tipi di criteri limitano il numero massimo di risorse di elaborazione a due. Nella prima esecuzione vengono utilizzati i criteri EnhanceScheduleGroupLocality , ovvero l'impostazione predefinita, mentre nella seconda vengono utilizzati i criteri EnhanceForwardProgress. Con i criteri EnhanceScheduleGroupLocality l'utilità di pianificazione esegue tutte le attività di un gruppo di pianificazione finché ogni attività non viene completata o restituita. Con i criteri EnhanceForwardProgress l'utilità di pianificazione si sposta nel gruppo di pianificazione successivo in modo round robin dopo il completamento o la restituzione di una sola l'attività.

Quando ogni gruppo di pianificazione contiene attività correlate, i criteri EnhanceScheduleGroupLocality determinano in genere un miglioramento delle prestazioni poiché la località della cache viene mantenuta tra le attività. I criteri EnhanceForwardProgress consentono alle attività di avanzare e sono utili quando è necessario pianificare equamente le risorse tra i gruppi di pianificazione.

Esempio

In questo esempio viene definita la classe work_yield_agent, che deriva da Concurrency::agent. La classe work_yield_agent esegue un'unità di lavoro, restituisce il contesto corrente, quindi esegue un'altra unità di lavoro. L'agente utilizza la funzione Concurrency::wait per restituire cooperativamente il contesto corrente in modo da consentire l'esecuzione di altri contesti.

In questo esempio vengono creati quattro oggetti work_yield_agent. Per illustrare come impostare i criteri dell'utilità di pianificazione per influenzare l'ordine di esecuzione degli agenti, i primi due agenti vengono associati a un gruppo di pianificazione mentre gli altri due agenti vengono associati a un altro gruppo di pianificazione. Nell'esempio viene utilizzato il metodo Concurrency::CurrentScheduler::CreateScheduleGroup per creare gli oggetti Concurrency::ScheduleGroup. Tutti e quattro gli agenti vengono eseguiti due volte, ogni volta con criteri di pianificazione diversi.

// scheduling-protocol.cpp
// compile with: /EHsc
#include <agents.h>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>

using namespace Concurrency;
using namespace std;

#pragma optimize( "", off )
// Simulates work by performing a long spin loop.
void spin_loop()
{
   for (int i = 0; i < 500000000; ++i)
   {
   }
}
#pragma optimize( "", on )

// Agent that performs some work and then yields the current context.
class work_yield_agent : public agent
{
public:
   explicit work_yield_agent(
      unsigned int group_number, unsigned int task_number)
      : _group_number(group_number)
      , _task_number(task_number)
   {
   }

   explicit work_yield_agent(Scheduler& scheduler,
      unsigned int group_number, unsigned int task_number)
      : agent(scheduler)
      , _group_number(group_number)
      , _task_number(task_number)
   {
   }

   explicit work_yield_agent(ScheduleGroup& group,
      unsigned int group_number, unsigned int task_number)
      : agent(group)       
      , _group_number(group_number)
      , _task_number(task_number)
   {
   }

protected:
   // Performs the work of the agent.   
   void run()
   {
      wstringstream header, ss;

      // Create a string that is prepended to each message.
      header << L"group " << _group_number 
             << L",task " << _task_number << L": ";

      // Perform work.
      ss << header.str() << L"first loop..." << endl;
      wcout << ss.str();
      spin_loop();

      // Cooperatively yield the current context. 
      // The task scheduler will then run all blocked contexts.
      ss = wstringstream();
      ss << header.str() << L"waiting..." << endl;
      wcout << ss.str();
      Concurrency::wait(0);

      // Perform more work.
      ss = wstringstream();
      ss << header.str() << L"second loop..." << endl;
      wcout << ss.str();
      spin_loop();

      // Print a final message and then set the agent to the 
      // finished state.
      ss = wstringstream();
      ss << header.str() << L"finished..." << endl;
      wcout << ss.str();

      done();
   }  

private:
   // The group number that the agent belongs to.
   unsigned int _group_number;
   // A task number that is associated with the agent.
   unsigned int _task_number;
};

// Creates and runs several groups of agents. Each group of agents is associated 
// with a different schedule group.
void run_agents()
{
   // The number of schedule groups to create.
   const unsigned int group_count = 2;
   // The number of agent to create per schedule group.
   const unsigned int tasks_per_group = 2;

   // A collection of schedule groups.
   vector<ScheduleGroup*> groups;
   // A collection of agents.
   vector<agent*> agents;

   // Create a series of schedule groups. 
   for (unsigned int group = 0; group < group_count; ++group)
   {
      groups.push_back(CurrentScheduler::CreateScheduleGroup());

      // For each schedule group, create a series of agents.
      for (unsigned int task = 0; task < tasks_per_group; ++task)
      {
         // Add an agent to the collection. Pass the current schedule 
         // group to the work_yield_agent constructor to schedule the agent
         // in this group.
         agents.push_back(new work_yield_agent(*groups.back(), group, task));
      }
   }

   // Start each agent.
   for_each(agents.begin(), agents.end(), [](agent* a) {
      a->start();
   });

   // Wait for all agents to finsih.
   agent::wait_for_all(agents.size(), &agents[0]);

   // Free the memory that was allocated for each agent.
   for_each(agents.begin(), agents.end(), [](agent* a) {
      delete a;
   });

   // Release each schedule group.
   for_each(groups.begin(), groups.end(), [](ScheduleGroup* group) {
      group->Release();
   });
}

int wmain()
{
   // Run the agents two times. Each run uses a scheduler
   // policy that limits the maximum number of processing resources to two.

   // The first run uses the EnhanceScheduleGroupLocality 
   // scheduling protocol. 
   wcout << L"Using EnhanceScheduleGroupLocality..." << endl;
   CurrentScheduler::Create(SchedulerPolicy(3, 
      MinConcurrency, 1,
      MaxConcurrency, 2,
      SchedulingProtocol, EnhanceScheduleGroupLocality));

   run_agents();
   CurrentScheduler::Detach();

   wcout << endl << endl;

   // The second run uses the EnhanceForwardProgress 
   // scheduling protocol. 
   wcout << L"Using EnhanceForwardProgress..." << endl;
   CurrentScheduler::Create(SchedulerPolicy(3, 
      MinConcurrency, 1,
      MaxConcurrency, 2,
      SchedulingProtocol, EnhanceForwardProgress));

   run_agents();
   CurrentScheduler::Detach();
}

Questo esempio produce l'output che segue.

Using EnhanceScheduleGroupLocality...
group 0,task 0: first loop...
group 0,task 1: first loop...
group 0,task 0: waiting...
group 1,task 0: first loop...
group 0,task 1: waiting...
group 1,task 1: first loop...
group 1,task 0: waiting...
group 0,task 0: second loop...
group 1,task 1: waiting...
group 0,task 1: second loop...
group 0,task 0: finished...
group 1,task 0: second loop...
group 0,task 1: finished...
group 1,task 1: second loop...
group 1,task 0: finished...
group 1,task 1: finished...


Using EnhanceForwardProgress...
group 0,task 0: first loop...
group 1,task 0: first loop...
group 0,task 0: waiting...
group 0,task 1: first loop...
group 1,task 0: waiting...
group 1,task 1: first loop...
group 0,task 1: waiting...
group 0,task 0: second loop...
group 1,task 1: waiting...
group 1,task 0: second loop...
group 0,task 0: finished...
group 0,task 1: second loop...
group 1,task 0: finished...
group 1,task 1: second loop...
group 0,task 1: finished...
group 1,task 1: finished...

Entrambi i tipi di criteri producono la stessa sequenza di eventi. Tuttavia, i criteri che utilizzano EnhanceScheduleGroupLocality avviano entrambi gli agenti che fanno parte del primo gruppo di pianificazione prima di avviare gli agenti che fanno parte del secondo gruppo. I criteri che utilizzano EnhanceForwardProgress avviano un agente del primo gruppo, quindi avviano il primo agente del secondo gruppo.

Compilazione del codice

Copiare il codice di esempio e incollarlo in un progetto di Visual Studio o incollarlo in un file denominato scheduling-protocol.cpp, quindi eseguire il comando seguente in una finestra del prompt dei comandi di Visual Studio 2010.

cl.exe /EHsc scheduling-protocol.cpp

Vedere anche

Concetti

Agenti asincroni

Altre risorse

Gruppi di pianificazione