Cómo: Usar grupos de programación para influir en el orden de ejecución
En el Runtime de simultaneidad, el orden en el que se programan las tareas no es determinista. Sin embargo, puede utilizar directivas de programación para influir en el orden en el que se ejecutan las tareas. En este tema se muestra cómo usar grupos de programación junto con la directiva de programador concurrency::SchedulingProtocol para influir en el orden en que se ejecutan las tareas.
En el ejemplo se ejecuta un conjunto de tareas dos veces, cada una con una directiva de programación diferente. Ambas directivas limitan el número máximo de recursos de procesamiento a dos. La primera ejecución usa la directiva EnhanceScheduleGroupLocality
(el valor predeterminado) y la segunda usa la directiva EnhanceForwardProgress
. Con la directiva EnhanceScheduleGroupLocality
, el programador ejecuta todas las tareas de un grupo de programación hasta que todas las tareas finalizan o dan paso a otra. Con la directiva EnhanceForwardProgress
, el programador se mueve al siguiente grupo de programación en un comportamiento por turnos inmediatamente después de que una tarea finaliza o produce un resultado.
Cuando cada grupo de programación contiene tareas relacionadas, la directiva EnhanceScheduleGroupLocality
suele suponer una mejora en el rendimiento, ya que el emplazamiento en caché se conserva entre las tareas. La directiva EnhanceForwardProgress
permite a las tareas progresar y es útil cuando se precisa equidad de programación entre los grupos de programación.
Ejemplo
En este ejemplo se define la clase work_yield_agent
, que se deriva de concurrency::agent. La clase work_yield_agent
realiza una unidad de trabajo, produce el contexto actual y, a continuación, realiza otra unidad de trabajo. El agente emplea la función concurrency::wait para ceder de forma cooperativa el contexto actual, de forma que se puedan ejecutar otros contextos.
En este ejemplo se crean cuatro objetos work_yield_agent
. Para mostrar cómo establecer directivas del programador para incluir en el orden en que se ejecutan los agentes, el ejemplo asocia los dos primeros agentes a un grupo de programación y los otros dos a otro. En el ejemplo se usa el método concurrency::CurrentScheduler::CreateScheduleGroup para crear los objetos concurrency::ScheduleGroup. En el ejemplo se ejecutan los cuatro agentes dos veces, cada vez que con una directiva de programación diferente.
// 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();
}
Este ejemplo produce el siguiente resultado:
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...
Ambas directivas producen la misma secuencia de eventos. Sin embargo, la directiva que utiliza EnhanceScheduleGroupLocality
inicia los dos agentes que forman parte del primer grupo de programación antes de iniciar los agentes que forman parte del segundo grupo. La directiva que utiliza EnhanceForwardProgress
inicia un agente del primer grupo y, a continuación, inicia el primer agente del segundo grupo.
Compilar el código
Copie el código de ejemplo y péguelo en un proyecto de Visual Studio o en un archivo denominado scheduling-protocol.cpp
y, después, ejecute el siguiente comando en una ventana del símbolo del sistema de Visual Studio.
cl.exe /EHsc scheduling-protocol.cpp