这是 MPI 标准(版本 4.1)的示例。
我们有 3 个进程和 3 个通信器。标准规定
不可能对所有通信器执行阻塞集体操作,因为不存在调用它们的无死锁顺序。但是,可以轻松地使用非阻塞集体操作来实现此任务。
并提供此代码片段
MPI_Request reqs[2];
switch(rank) {
case 0:
MPI_Iallreduce(sbuf1, rbuf1, count, dtype, MPI_SUM, comm1, &reqs[0]);
MPI_Iallreduce(sbuf3, rbuf3, count, dtype, MPI_SUM, comm3, &reqs[1]);
break;
case 1:
MPI_Iallreduce(sbuf1, rbuf1, count, dtype, MPI_SUM, comm1, &reqs[0]);
MPI_Iallreduce(sbuf2, rbuf2, count, dtype, MPI_SUM, comm2, &reqs[1]);
break;
case 2:
MPI_Iallreduce(sbuf2, rbuf2, count, dtype, MPI_SUM, comm2, &reqs[0]);
MPI_Iallreduce(sbuf3, rbuf3, count, dtype, MPI_SUM, comm3, &reqs[1]);
break;
}
MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE);
我在同一代码片段的阻塞版本中没有看到死锁,即在
switch(rank) {
case 0:
MPI_Allreduce(sbuf1, rbuf1, count, dtype, MPI_SUM, comm1);
MPI_Allreduce(sbuf3, rbuf3, count, dtype, MPI_SUM, comm3);
break;
case 1:
MPI_Allreduce(sbuf1, rbuf1, count, dtype, MPI_SUM, comm1);
MPI_Allreduce(sbuf2, rbuf2, count, dtype, MPI_SUM, comm2);
break;
case 2:
MPI_Allreduce(sbuf2, rbuf2, count, dtype, MPI_SUM, comm2);
MPI_Allreduce(sbuf3, rbuf3, count, dtype, MPI_SUM, comm3);
break;
}
所以时序图大约是
我错过了什么吗?也许“僵局”标准并不意味着完全停滞。
根据维基百科,
如果一个进程保持无限期地无法更改其状态,因为它请求的资源正在被本身正在等待的另一个进程使用,则系统被认为处于死锁状态。
(我强调)
该标准意味着不能保证这是无死锁的,并且假设是这样是一个不正确的程序。
但是,MPI 实现可能在幕后执行非阻塞操作,这样是可行的,但假设该行为不符合标准,并且不能保证其他一些 MPI 实现将起作用或更复杂的示例将起作用。