Лаборатория Параллельных информационных технологий НИВЦ МГУ
Опубликован: 22.04.2008 | Доступ: свободный | Студентов: 1177 / 413 | Оценка: 4.30 / 4.19 | Длительность: 08:05:00
Специальности: Программист
Лекция 3:

Передача/прием сообщений между отдельными процессами

< Лекция 2 || Лекция 3: 123 || Лекция 4 >

Тупиковые ситуации (deadlock)

Использование блокирующих процедур приема и посылки связано с возможным возникновением тупиковой ситуации. Предположим, что работают два параллельных процесса, и они должны обменяться данными. Было бы вполне естественно в каждом процессе сначала воспользоваться процедурой MPI_SEND, а затем процедурой MPI_RECV. НО именно этого и не стоит делать. Дело в том, что мы заранее не знаем, как реализована процедура MPI_SEND. Если разработчики для гарантии корректного повторного использования буфера посылки заложили схему, при которой посылающий процесс ждет начала приема, то возникнет классический тупик. Первый процесс не может вернуться из процедуры посылки, поскольку второй не начинает прием сообщения. А второй процесс не может начать прием сообщения, поскольку сам по похожей причине застрял на посылке.

Еще хуже ситуация, когда оба процесса сначала попадают на блокирующую процедуру приема MPI_RECV, а лишь затем на посылку данных. В таком случае тупик возникнет независимо от способа реализации процедур приема и посылки данных.

Таблица 3.2.
процесс 0 процесс 1
MPI_RECV от процесса 1 MPI_SEND процессу 1
MPI_RECV от процесса 0 MPI_SEND процессу 0

Возникает тупик!

Таблица 3.3.
процесс 0 процесс 1
MPI_SEND процессу 1 MPI_RECV от процесса 1
MPI_SEND процессу 0 MPI_RECV от процесса 0

Может возникнуть тупик!

Рассмотрим различные способы разрешения тупиковых ситуаций.

  1. Простейшим вариантом разрешения тупиковой ситуации будет изменение порядка следования процедур посылки и приема сообщения на одном из процессов, как показано ниже.
    Таблица 3.4.
    процесс 0 процесс 1
    MPI_SEND процессу 1 MPI_RECV от процесса 1
    MPI_RECV от процесса 0 MPI_SEND процессу 0

    Тупик не возникает!

  2. Другим вариантом разрешения тупиковой ситуации может быть использование неблокирующих операций. Заменим вызов процедуры приема сообщения с блокировкой на вызов процедуры MPI_IRECV . Расположим его перед вызовом процедуры MPI_SEND , т.е. преобразуем фрагмент следующим образом:
    Таблица 3.5.
    процесс 0 процесс 1
    MPI_SEND процессу 1 MPI_RECV от процесса 1
    MPI_IRECV от процесса 0 MPI_SEND процессу 0 MPI_WAIT

    Тупик не возникает!

    В такой ситуации тупик гарантированно не возникнет, поскольку к моменту вызова процедуры MPI_SEND запрос на прием сообщения уже будет выставлен, а значит, передача данных может начаться. При этом рекомендуется выставлять процедуру MPI_IRECV В программе как можно раньше, чтобы раньше предоставить возможность начала пересылки и максимально использовать преимущества асинхронности.

  3. Третьим вариантом разрешения тупиковой ситуации может быть использование процедуры MPI_SENDRECV.
    MPI_SENDRECV(SBUF, SCOUNT, STYPE, DEST, STAG, RBUF, RCOUNT,
    RTYPE, SOURCE, RTAG, COMM, STATUS, IERR)
    <type> SBUF(*), RBUF(*)
    INTEGER SCOUNT, STYPE, DEST, STAG, RCOUNT, RTYPE, SOURCE,
    RTAG, COMM, STATUS(MPI_STATUS_SIZE), IERR

Процедура выполняет совмещенные прием и передачу сообщений с блокировкой. По вызову данной процедуры осуществляется посылка SCOUNT элементов типа STYPE ИЗ массива SBUF С тегом STAG процессу с номером DEST в коммуникаторе сомм и прием в массив RBUF не более RCOUNT элементов типа RTYPE С тегом RTAG ОТ процесса с номером SOURCE в коммуникаторе сомм. Для принимаемого сообщения заполняется параметр STATUS. Принимающий и отправляющий процессы могут являться одним и тем же процессом. Буферы передачи и приема данных не должны пересекаться. Гарантируется, что при этом тупиковой ситуации не возникает. Сообщение, отправленное операцией MPI_SENDRECV, может быть принято обычным образом, и операция MPI_SENDRECV может принять сообщение, отправленное обычной операцией.

MPI_SENDRECV_REPLACE(BUF, COUNT, DATATYPE, DEST, STAG, SOURCE,
RTAG, COMM, STATUS, IERR)
<type> BUF(*)
INTEGER COUNT, DATATYPE, DEST, STAG, SOURCE, RTAG, COMM,
STATUS(MPI_STATUS_SIZE), IERR

Совмещенные прием и передача сообщений с блокировкой через общий буфер BUF. Принимаемое сообщение не должно превышать по размеру отправляемое сообщение, а передаваемые и принимаемые данные должны быть одного типа.

В следующем примере операции двунаправленного обмена с соседними процессами в кольцевой топологии производятся при помощи двух вызовов процедуры MPI_SENDRECV. При этом гарантированно не возникает тупиковой ситуации.

program example12
include 'mpif.h'
integer ierr, rank, size, prev, next, buf(2)
integer status1 (MPI_STATUS_SIZE), status2(MPI_STATUS_SIZE)
call MPI_INIT(ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
prev = rank - 1
next = rank + 1
if(rank .eq. 0) prev = size - 1
if(rank .eq. size - 1) next = 0
call MPI_SENDRECV(rank, 1, MPI_INTEGER, prev, 
 6,& buf(2), 1, MPI_INTEGER, next, 6,
  & MPI_COMM_WORLD, status2, ierr)
call MPI_SENDRECV(rank, 1, MPI_INTEGER, next, 
 5,& buf(1), 1, MPI_INTEGER, prev, 5,
  & MPI_COMM_WORLD, status1, ierr)
print *, 'process ', rank, & ' prev=', buf(1), ' next=', buf(2)
call MPI_FINALIZE(ierr)
end

Задания

  • Какими атрибутами обладает в MPI каждое посылаемое сообщение?
  • Можно ли сообщение, отправленное с помощью блокирующей операции посылки, принять неблокирующей операцией приема?
  • Что гарантирует блокировка при отправке/приеме сообщений?
  • Можно ли в качестве тегов при посылке различных сообщений в программе всегда использовать одно и то же число?
  • Как принять любое сообщение от любого процесса?
  • Как принимающий процесс может определить длину полученного сообщения?
  • Можно ли при посылке сообщения использовать константы MPI_ANY_SOURCE И MPI ANY_TAG?
  • Можно ли, не принимая сообщения, определить его атрибуты?
  • Будет ли корректна программа, в которой посылающий процесс указывает в качестве длины буфера число 10, а принимающий процесс -число 2 0? Если да, то сколько элементов массива будет реально переслано между процессами?
  • Сравнить эффективность реализации различных видов пересылок данных с блокировкой (MPI_SEND, MPI_BSEND, MPI_SSEND, MPI_RSEND) между двумя выделенными процессорами.
  • Что означает завершение операции для различных видов пересылки данных с блокировкой?
  • Определить максимально допустимую длину посылаемого сообщения в данной реализации MPI.
  • Реализовать скалярное произведение распределенных между процессорами векторов.
  • Сравнить эффективность реализации пересылок данных между двумя выделенными процессорами с блокировкой и без блокировки.
  • Определить, возможно ли в данной реализации MPI совмещение асинхронных пересылок данных и выполнения арифметических операций.
  • Как с помощью процедуры MPI_TEST смоделировать функциональность процедуры MPI_WAIT?
  • В чем состоят различия в использовании процедур MPI_WAITALL, MPI_WAITANY и MPI_WAITSOME? Как смоделировать их функциональность при помощи процедуры MPI_WAIT?
  • Что произойдет при осуществлении обмена данными с процессом MPI_PROC_NULL?
  • Реализовать при помощи посылки сообщений типа точка-точка следующие схемы коммуникации процессов:
    • передача данных по кольцу, два варианта: "эстафетная палочка" (очередной процесс дожидается сообщения от предыдущего и потом посылает следующему) и "сдвиг" (одновременные посылка и прием сообщений);
    • master-slave (все процессы общаются с одним выделенным процессом);
    • пересылка данных от каждого процесса каждому.
  • Исследовать эффективность коммуникационных схем из предыдущего задания в зависимости от числа использованных процессов и объема пересылаемых данных, изучить возможности оптимизации.
  • Определить выигрыш, который можно получить при использовании отложенных запросов на взаимодействие.
  • Сравнить эффективность реализации функции MPI_SENDRECV Смоделированием той же функциональности при помощи неблокирующих операций.
< Лекция 2 || Лекция 3: 123 || Лекция 4 >