fortran+MPIコーディング入門(3) 1対1通信 

前回は多対多の集団通信について学びました。ですが、実際「となりのプロセスにだけデータを送りたい」というような場合もよくあります。今回はそういう場合に使う1対1通信について学びます。

目次（予定）

1. [並列処理とは](https://qiita.com/Bluepost59/items/58e111fe486c4aeb48bf)
1. [プロセス並列の基本] (https://qiita.com/items/5cce1174ac48367948bc)
2. [集団通信](https://qiita.com/Bluepost59/items/f1871b37669b38352a7c)
3. 1対1通信 ←今ココ
4. [オブジェクト指向コードの並列化](https://qiita.com/Bluepost59/items/1e0953c82b633a9d7602)

# 1対1通信
## MPI_sendとMPI_recv
文字通りデータを送ります。

ブロッキング通信なので、通信が正常に終了しなければ終了しません。

### サンプル
このサンプルでは、右隣に自身のランクを送信し、左隣からランクを受信します。

```fortran
subroutine test_01()
    integer :: n_data
    integer :: dest     ! 送り先
    integer :: origin   ! 受信データの送信元
    
    integer :: buf                  ! 受け取るデータ
    integer,allocatable :: ista(:)  ! ステータス格納用

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 01"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_send(buf, n_data, mpi_integer , dest, &
         me, mpi_comm_world, ierr)
    call mpi_recv(buf, n_data, mpi_integer, origin, &
         origin, mpi_comm_world,ista,ierr)

    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_01
```

## MPI_sendrecv
sendとrecvは基本的にセットで呼ばれるので、両方まとめたものがこれです。

```fortran
subroutine test_02()
    integer :: n_data
    integer :: dest
    integer :: origin
    
    integer :: buf
    integer,allocatable :: ista(:) 

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 02"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_sendrecv(me, n_data, mpi_integer , dest, me,&
         buf, n_data,mpi_integer, origin, origin, & 
         mpi_comm_world, ista, ierr)

    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_02
```

## MPI_IsendとMPI_Irecv
mpi_sendとmpi_recvはブロッキング通信ですが、ブロッキングでないものがmpi_isendとmpi_irecvです。

送受信を完了するにはMPI_waitを使う必要があります。

```fortran:
subroutine test_03()
    integer :: n_data
    integer :: dest
    integer :: origin
    
    integer :: buf
    integer :: ireq
    integer,allocatable :: ista(:) 

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 03"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_isend(me, n_data, mpi_integer , dest, &
         me, mpi_comm_world, ireq, ierr)
    call mpi_irecv(buf, n_data, mpi_integer, origin, &
         origin, mpi_comm_world,ireq,ierr)

    call mpi_wait(ireq, ista, ierr)
    
    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_03
```

#デッドロック
集団通信と異なり、1対1通信では厄介な問題が生じることがあります。それはデッドロックです。

一般に**デッドロック**とは、複数の処理同士が互いの処理の終了を待っている状態になり、処理が完全に停止してしまうことを言います。排他制御などをかじったことがある人なら知っているかも知れません。

MPIでは次のようにしてデッドロックが生じます。以下のコードはデッドロックを起こし得ます。

```fortran
! n_dataはかなり大きい
call mpi_recv(buf_recv, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ista,ierr)
call mpi_send(buf_send, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ,ierr)
```

このコードを実行すると、両方がデータの受信待ちになるものの、データをsendしてくれる相手がいないので、デッドロックになります。特にデータサイズが大きいときにおこりやすいです。

逆にしても同じです。

```fortran
call mpi_send(buf_send, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ierr)
call mpi_recv(buf_recv, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ista,ierr)
```

mpi_sendは受信する相手がいないと正常に終了しません。なので順番を変えてもデッドロックになります。

厄介なことに、デッドロックは場合によっては起こったり起こらなかったりします。ログが出ることもありません。その上abortするわけでもありません[^1] 。そのため「MPIプログラムが沈黙したらデッドロックを疑う」と知っていなければいけません。

## 解決策
### 1.MPI_sendrecvを使う
MPI_sendrecvを使えばデッドロックを防げます。

### 2. MPI_isendとMPI_irecvに置き換える
デッドロックはブロッキング通信であることが原因なので、ノンブロッキングであるMPI_isendとMPI_irecvに置き換えれば解消できます。

# 構造体やクラスを通信するには
さて、ここまでテストで扱ってきたmpiコマンドは、すべてmpiデータ型（```mpi_integer```,```mpi_double_precision```など）を引数に取ってきました。ではここで自分で定義した構造体やクラスなどを通信するにはどうすればよいでしょうか。

実は僕も1行で書けるコードは知りません。```mpi_datatype_null```を駆使すれば行けるのかもしれないですが、僕が見たことがあるコードでは、構造体を1種類しか扱っていなかったので、その構造体に対してsendやbcastをそれぞれ定義していました。

これについては次回。

# 参考サイト
* いつもの概説サイト<br>http://www.cv.titech.ac.jp/~hiro-lab/study/mpi_reference/chapter4.html

* デッドロックについて詳説<br>https://qiita.com/kaityo256/items/a8690d4dc0d82dd7617c



[^1]: この「abortしない」というのが非常に厄介です。「月曜日に結果を見るつもりで金曜日に計算を回してうまく行ってそうだったのに、月曜日になっても計算が終了しておらず結果も白紙のまま」ということが多々ありました。orz