1.5 非阻塞式通信
非阻塞式通信方式是MPI的重要特性之一。非阻塞式通信初始化后,不需等待通信完成后再进行操作。该特性具有两个优势:一是非阻塞式通信实现了通信与其他计算等操作异步执行;二是针对复杂的通信模式,不需要严格的通信顺序和内存空间管理。为了更好地理解第二个优势,执行如下代码,其中partner是其他进程的进程号。
MPI_Send需要将buf中的1亿个整数发送到partner进程上。若发送操作未申请到内存,程序将处于等待状态,直到内存申请成功。同理,调用MPI_Recv等接收操作后,也需要等待内存申请成功。在本程序示例中,两个进程都先调用MPI_Send执行发送操作,所以程序有可能永远处于等待状态。该程序运行是不安全的,程序能否正确执行依赖于内存申请情况以及发送和接收操作的合理匹配。
针对死锁的问题,经典的解决办法如图1-4所示,
通过改变发送和接收顺序消除进程间等待。该方法仅适用于通信模式简单或者程序编译时通信方式已明确的情况下。复杂的通信模式有时在程序运行时才决定通信方式,从而不适用该方法。针对复杂的通信模式,解决方法是在通信结束前MPI通信操作可以返回,无需等待通信过程结束。在MPI中,这种通信方式称之为非阻塞式通信,通常在函数名称前加入字母“I”,例如MPI_Send的非阻塞发送函数名为MPI_Isend。非阻塞式通信方式的参数设置类似于阻塞式通信方式。非阻塞比阻塞式通信方式增加了一个输出参数MPI_Request。MPI_Request是用于查询操作状态和等待操作完成的句柄。使用非阻塞式通信方式进行发送和接收的程序实例如图1-5所示。用户需要调用一个测试或者等待函数完成通信操作。MPI_Wait函数会阻塞程序执行,直到通信操作完成。MPI_Test函数不会阻塞程序执行,该函数执行后立即返回,用于测试通信操作是否完成。同时,MPI提供组测试和组等待函数,用于同时测试和等待多个通信操作,例如图1-5中MPI_Waitall函数。
在很多应用程序中,需要重复相同的通信方式。针对重复的通信需求,MPI提供重复非阻塞式通信方式,例如通过使用MPI_Send_init和MPI_Recv_init函数实现重复通信初始化。在重复通信模式中,通信操作初始化后并没有启动消息通信,消息通信由MPI_Start函数触发。如果消息通信结束,例如调用MPI_Wait后返回结果,可通过调用MPI_Start再次启动消息通信。当不再需要进行消息通信时,通过调用MPI_Request_free释放通信对象。