Die Boost C++ Bibliotheken

Asynchroner Datenaustausch

Boost.MPI unterstützt neben den blockierenden Methoden send() und recv() auch einen asynchronen Datenaustausch. Sie greifen hierzu auf die Methoden isend() und irecv() zu. Die Namen beginnen mit einem i, um darauf hinzuweisen, dass diese Methoden sofort – auf Englisch immediate – zurückkehren.

Beispiel 47.7. Asynchroner Datenempfang mit irecv()
#include <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <string>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    std::string s;
    boost::mpi::request r = world.irecv(boost::mpi::any_source, 16, s);
    if (r.test())
      std::cout << s << '\n';
    else
      r.cancel();
  }
  else
  {
    std::string s = "Hello, world!";
    world.send(0, 16, s);
  }
}

Beispiel 47.7 verwendet die bereits bekannte blockierende Methode send(), um den String Hello, world! zu senden. Der Datenempfang findet mit der asynchronen Methode irecv() statt. Diese Methode erwartet die gleichen Parameter wie recv(). Der entscheidende Unterschied ist: Wenn irecv() zurückkehrt, ist nicht garantiert, dass in der Variablen s Daten empfangen wurden.

irecv() gibt ein Objekt vom Typ boost::mpi::request zurück. Sie können für dieses Objekt test() aufrufen, um festzustellen, ob Daten empfangen wurden oder nicht. Diese Methode gibt ein Ergebnis vom Typ bool zurück. Sie können test() beliebig oft aufrufen. Da es sich bei irecv() um eine asynchrone Methode handelt, kann es sein, dass bei einem ersten Aufruf zum Beispiel false, beim zweiten Aufruf jedoch true zurückgegeben wird. In einem derartigen Fall wäre die asynchrone Operation nach dem ersten Aufruf, aber vor dem zweiten abgeschlossen worden.

Im Beispiel 47.7 wird test() nur einmal aufgerufen. Wurden Daten in s empfangen, wird die Variable auf die Standardausgabe ausgegeben. Wurden keine Daten empfangen, wird die asynchrone Operation abgebrochen. Dies geschieht über den Aufruf von cancel().

Wenn Sie Beispiel 47.7 mehrfach ausführen, erhalten Sie manchmal die Ausgabe Hello, world! und manchmal keine Ausgabe. Ob eine Ausgabe erfolgt, hängt davon ab, ob die Daten vor dem Aufruf von test() empfangen werden konnten.

Beispiel 47.8. Auf mehrere asynchrone Operationen mit wait_all() warten
#include <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <string>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    boost::mpi::request requests[2];
    std::string s[2];
    requests[0] = world.irecv(1, 16, s[0]);
    requests[1] = world.irecv(2, 16, s[1]);
    boost::mpi::wait_all(requests, requests + 2);
    std::cout << s[0] << "; " << s[1] << '\n';
  }
  else if (world.rank() == 1)
  {
    std::string s = "Hello, world!";
    world.send(0, 16, s);
  }
  else if (world.rank() == 2)
  {
    std::string s = "Hello, moon!";
    world.send(0, 16, s);
  }
}

Sie können für ein Objekt vom Typ boost::mpi::request mehrfach test() aufrufen, um festzustellen, wann die asynchrone Operation abgeschlossen wurde. Sie können jedoch auch wie im Beispiel 47.8 eine blockierende Funktion wie boost::mpi::wait_all() aufrufen. Auch wenn es sich hierbei um eine blockierende Funktion handelt: Der Vorteil von boost::mpi::wait_all() ist, dass diese Funktion auf den Abschluss mehrerer asynchroner Operationen warten kann. Im Falle von boost::mpi::wait_all() müssen alle asynchronen Operationen abgeschlossen sein, bevor boost::mpi::wait_all() zurückkehrt.

Im Beispiel 47.8 sendet der Prozess mit Rang 1 Hello, world! und der Prozess mit Rang 2 Hello, moon!. Da weder klar ist, in welcher Reihenfolge die Daten im Prozess mit Rang 0 empfangen werden, noch dass die Reihenfolge eine Rolle spielen würde, werden die Daten mit irecv() empfangen. Da die Datenausgabe jedoch erst dann erfolgen kann, wenn alle asynchronen Operationen abgeschlossen und alle Daten empfangen wurden, werden die Rückgabeobjekte vom Typ boost::mpi::request an boost::mpi::wait_all() übergeben. boost::mpi::wait_all() erwartet zwei Iteratoren, die auf den Anfang und das Ende eines Containers zeigen. Da es sich im Beispiel 47.8 um ein Array handelt, werden ein Zeiger auf den Anfang und ein Zeiger auf das Ende des Arrays übergeben.

Boost.MPI bietet neben boost::mpi::wait_all() weitere blockierende Funktionen an, um auf den Abschluss mehrerer asynchroner Operationen zu warten. boost::mpi::wait_any() kehrt zurück, wenn genau eine asynchrone Operation abgeschlossen wurde. boost::mpi::wait_some() kehrt zurück, wenn eine oder mehrere asynchrone Operationen abgeschlossen wurden. Beide Funktionen geben ein Ergebnis vom Typ std::pair zurück, das Auskunft darüber gibt, welche asynchrone Operation oder Operationen abgeschlossen wurden.

Neben Funktionen, um auf den Abschluss asynchroner Operationen zu warten, bietet Boost.MPI mit boost::mpi::test_all(), boost::mpi::test_any() und boost::mpi::test_some() auch eine Möglichkeit, den Status mehrerer asynchroner Operationen mit einem einzigen Funktionsaufruf zu überprüfen. Diese Funktionen sind nicht-blockierend und kehren sofort zurück.