agency-library / agency

Execution primitives for C++
http://agency-library.github.io
BSD 3-Clause "New" or "Revised" License
154 stars 27 forks source link

Investigate simple syntax for shared parameters for common scenarios #236

Open jaredhoberock opened 8 years ago

jaredhoberock commented 8 years ago

It might be possible to teach bulk_invoke() and friends how to introspect the types of shared parameter objects to create automatically by inspecting a lambda's parameter types. This would avoid needing to use share<T>().

jaredhoberock commented 8 years ago

It looks like this will work, at least for bulk_invoke(). Will have to verify it also works for bulk_async() & bulk_then().

This sort of usage is possible:

std::mutex mut;
agency::bulk_invoke(agency::par(10), [&](agency::parallel_agent& self, int& shared_integer)
{
  mut.lock();
  std::cout << "shared_integer: " << shared_integer << std::endl;
  ++shared_integer;
   mut.unlock();
});

This syntax shares the shared_integer parameters automatically, at the innermost levels of the execution hierarchy. Another convention would be to create a single shared_integer parameter at the outermost level. One reason to prefer the innermost level is that it's already possible to share something among all the agents by capturing a variable by reference, as this example does with the std::mutex variable.

We could also come up with some additional syntax to explicitly specify the level at which to share individual parameters. Something like this may be possible:

std::mutex mut;
constexpr int level = 0;
agency::bulk_invoke(agency::par(10), [&](agency::parallel_agent& self, shared<level,int>& shared_parm)
{
  int& shared_integer = shared_parm.get();
  mut.lock();
  std::cout << "shared_integer: " << shared_integer << std::endl;
  ++shared_integer;
   mut.unlock();
});

We might also be able to reuse the shared<level,T> type to store dynamically-allocated shared objects as well.

jaredhoberock commented 7 years ago

One drawback to this approach is that there's not really a way to initialize the values of the shared parameters besides simply calling their default constructor.

It might be worth investigating a way to call some other special constructor. For example, it would be convenient to be able to automatically create a container whose size is initialized to the number of execution agents sharing the vector. The problem is that the variable's lifetime begins before the execution agents -- so we can't just pass self to the variable's constructor. Even if we could, it's not clear that would be helpful -- which execution agent would we choose? And how would we guarantee that the object has been constructed before all agents call the user's function?