Comment : utiliser des groupes de planification pour influencer l’ordre d’exécution
Dans le runtime d’accès concurrentiel, l’ordre dans lequel les tâches sont planifiées est non déterministe. Toutefois, vous pouvez utiliser des stratégies de planification pour influencer l’ordre dans lequel les tâches s’exécutent. Cette rubrique montre comment utiliser des groupes de planification avec la stratégie de planificateur concurrency ::ScheduleProtocol pour influencer l’ordre dans lequel les tâches s’exécutent.
L’exemple exécute un ensemble de tâches deux fois, chacune avec une stratégie de planification différente. Les deux stratégies limitent le nombre maximal de ressources de traitement à deux. La première exécution utilise la EnhanceScheduleGroupLocality
stratégie, qui est la valeur par défaut, et la deuxième exécution utilise la EnhanceForwardProgress
stratégie. Sous la EnhanceScheduleGroupLocality
stratégie, le planificateur exécute toutes les tâches d’un groupe de planification jusqu’à ce que chaque tâche se termine ou génère. Sous la EnhanceForwardProgress
stratégie, le planificateur passe au groupe de planification suivant d’une manière de tourniquet une fois qu’une seule tâche se termine ou génère.
Lorsque chaque groupe de planification contient des tâches associées, la EnhanceScheduleGroupLocality
stratégie entraîne généralement une amélioration des performances, car la localité du cache est conservée entre les tâches. La EnhanceForwardProgress
stratégie permet aux tâches d’avancer et est utile lorsque vous avez besoin d’une planification équitable entre les groupes de planification.
Exemple
Cet exemple définit la work_yield_agent
classe, qui dérive de concurrency ::agent. La work_yield_agent
classe effectue une unité de travail, génère le contexte actuel, puis effectue une autre unité de travail. L’agent utilise la fonction concurrency ::wait pour générer de manière coopérative le contexte actuel afin que d’autres contextes puissent s’exécuter.
Cet exemple crée quatre work_yield_agent
objets. Pour illustrer comment définir des stratégies de planificateur pour affecter l’ordre dans lequel les agents s’exécutent, l’exemple associe les deux premiers agents à un groupe de planification et les deux autres agents à un autre groupe de planification. L’exemple utilise la méthode concurrency ::CurrentScheduler ::CreateScheduleGroup pour créer les objets concurrency ::ScheduleGroup . L’exemple exécute les quatre agents deux fois, chaque fois avec une stratégie de planification différente.
// 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(begin(agents), end(agents), [](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(begin(agents), end(agents), [](agent* a) {
delete a;
});
// Release each schedule group.
for_each(begin(groups), end(groups), [](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();
}
Cet exemple produit la sortie suivante.
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...
Les deux stratégies produisent la même séquence d’événements. Toutefois, la stratégie qui utilise EnhanceScheduleGroupLocality
démarre les deux agents qui font partie du premier groupe de planification avant de démarrer les agents qui font partie du deuxième groupe. La stratégie qui utilise EnhanceForwardProgress
démarre un agent à partir du premier groupe, puis démarre le premier agent dans le deuxième groupe.
Compilation du code
Copiez l’exemple de code et collez-le dans un projet Visual Studio, ou collez-le dans un fichier nommé scheduling-protocol.cpp
, puis exécutez la commande suivante dans une fenêtre d’invite de commandes Visual Studio.
cl.exe /EHsc scheduling-protocol.cpp