捆绑发送接收
MPI_SENDRECV(sendbuf,sendcount,sendtype,dest,sendtag,recvbuf,recvcount,
recvtype, source,recvtag,comm,status)

  IN sendbuf
IN sendcount
IN sendtype
IN dest
IN sendtag
OUT recvbuf
IN recvcount
IN recvtype
IN source
IN recvtag
IN comm
OUT status
发送缓冲区起始地址(可选数据类型)
发送数据的个数(整型)
发送数据的数据类型(句柄)
目标进程标识(整型)
发送消息标识(整型)
接收缓冲区初始地址(可选数据类型)
最大接收数据个数(整型)
接收数据的数据类型(句柄)
源进程标识(整型)
接收消息标识(整型)
通信域(句柄)
返回的状态(status)
int MPI_Sendrecv(void *sendbuf, int sendcount,MPI_Datatype sendtype, int dest,
int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source,
int recvtag, MPI_Comm comm, MPI_Status *status)
MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG,
RECVBUF, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM,
STATUS, IERROR)
<type> SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVCOUNT,
RECVTYPE,SOURCE, RECVTAG, COMM,STATUS(MPI_STATUS_SIZE), IERROR

讲解
捆绑发送和接收操作把发送一个消息到一个目的地和从另一个进程接收一个消息合并到一个调用中,源和目的可以是相同的。捆绑发送接收操作虽然在语义上等同于一个发送操作和一个接收操作的结合,但是它可以有效地避免由于单独书写发送或接收操作时,由于次序的错误而造成的死锁。这是因为该操作由通信系统来实现,系统会优化通信次序,从而有效地避免不合理的通信次序,最大限度避免死锁的产生。
捆绑发送接收操作是不对称的,即一个由捆绑发送接收调用发出的消息可以被一个普通接收操作接收,一个捆绑发送接收调用可以接收一个普通发送操作发送的消息。
该操作执行一个阻塞的发送和接收,接收和发送使用同一个通信域,但是可能使用不同的标识。发送缓冲区和接收缓冲区必须分开,他们可以是不同的数据长度和不同的数据类型。

用捆绑发送接收语句来重新实现Jacobi叠代

program main
implicit none
include 'mpif.h'
integer totalsize,mysize,steps
parameter (totalsize=16)
parameter (mysize=totalsize/4,steps=10)

integer n, myid, numprocs, i, j,rc
real a(totalsize,mysize+2),b(totalsize,mysize+2)
integer begin_col,end_col,ierr
integer status(MPI_STATUS_SIZE)

call MPI_INIT( ierr )
call MPI_COMM_RANK( MPI_COMM_WORLD, myid, ierr )
call MPI_COMM_SIZE( MPI_COMM_WORLD, numprocs, ierr )
print *, "Process ", myid, " of ", numprocs, " is alive"

C 数组初始化
do j=1,mysize+2
do i=1,totalsize
a(i,j)=0.0
end do
end do
if (myid .eq. 0) then
do i=1,totalsize
a(i,2)=8.0
end do
end if
if (myid .eq. 3) then
do i=1,totalsize
a(i,mysize+1)=8.0
end do
end if
do i=1,mysize+2
a(1,i)=8.0
a(totalsize,i)=8.0
end do

C 开始迭代
do n=1,steps

C 从左向右平移数据
if (myid .eq. 0) then
call MPI_SEND(a(1,mysize+1),totalsize,MPI_REAL,myid+1,10,
* MPI_COMM_WORLD,ierr)
else if (myid .eq. 3) then
call MPI_RECV(a(1,1),totalsize,MPI_REAL,myid-1,10,
* MPI_COMM_WORLD,status,ierr)
else
call MPI_SENDRECV(a(1,mysize+1),totalsize,MPI_REAL,myid+1,10,
* a(1,1),totalsize,MPI_REAL,myid-1,10,
*MPI_COMM_WORLD,status,ierr)
end if

C 从右向左平移数据
if (myid .eq. 0) then
call MPI_RECV(a(1,mysize+2),totalsize,MPI_REAL,myid+1,10,
* MPI_COMM_WORLD,status,ierr)
else if (myid .eq. 3) then
call MPI_SEND(a(1,2),totalsize,MPI_REAL,myid-1,10,
* MPI_COMM_WORLD,ierr)
else
call MPI_SENDRECV(a(1,2),totalsize,MPI_REAL,myid-1,10,
*a(1,mysize+2),totalsize,MPI_REAL,myid+1,10,
* MPI_COMM_WORLD,status,ierr)
end if
begin_col=2
end_col=mysize+1
if (myid .eq. 0) then
begin_col=3
endif
if (myid .eq. 3) then
end_col=mysize
endif

do j=begin_col,end_col
do i=2,totalsize-1
b(i,j)=(a(i,j+1)+a(i,j-1)+a(i+1,j)+a(i-1,j))*0.25
end do
end do
do j=begin_col,end_col
do i=2,totalsize-1
a(i,j)=b(i,j)
end do
end do
end do
do i=2,totalsize-1
print *, myid,(a(i,j),j=begin_col,end_col)
end do

call MPI_Finalize(rc)
end

对于Jacobi迭代,发送和接收操作是成对出现的,因此特别适合使用捆绑发送接收操作调用。对于中间块来说,每一块都要向两侧的相邻块发送数据,同时也要从两侧的相邻块接收数据,可以非常方便地用MPI_SENDRECV调用来实现。如下图所示。


图8-2 用MPI_SENDRECV实现Jacobi迭代示意图

但是对于左侧和右侧的边界块,却不容易将各自的发送和接收操作合并到一个调用之中,因此在本例子中,对边界块仍编写单独的发送和接收语句。在下面的部分,当引入进程拓扑和虚拟进程后,就可以可以把边界块和内部块统一看待,全部通信都用MPI_SENDRECV语句实现。