我正在学习MPI-2并尝试使用MPI-2单边通信实现第一个简单功能:
让进程0托管一个固定大小data_size
的数组。
每个进程(包括0)将生成一个数组并与主机数组进行比较:
如果生成的数组的第一个元素小于主机数组的第一个元素,则将主机数组替换为生成的数组。
在代码中:
vector<int> v1 = {rank,rank+1,rank+2};
v = get_vec(vec);
if (v1[0] < v[0])
put_vec(vec,v1);
完整的代码位于底部。当然,我希望在所有生成的数组中,头部元素最小的数组最终应该在程序完成时位于主机数组中,因为最小的数组(本例中为[0,1,2])将替换其他数组并且不会被替换。
然而,在某些(罕见)场合,我得到了这样的输出:
$ mpiexec.exe -n 4 a.exe
#0 assigns v1 {0 ...} to host v {2 ...}
#1 assigns v1 {1 ...} to host v {2 ...}
1 2 3
,这似乎表明两个分配同时发生在主机数据上。我想我一定是误解了get_vec/putvec
中的锁定/解锁同步指令,或者在其他地方犯了一些明显的错误。
有人可以解释我应该如何修复我的代码以获得预期的输出?
提前致谢。
使用g++ -std=c++11 test.cpp -lmpi
编译的完整代码:
#include <mpi.h>
#include <stdlib.h>
#include <stdio.h>
#include <thread>
#include <chrono>
#include <iostream>
#include <vector>
using namespace std;
struct mpi_vector_t {
MPI_Win win;
int hostrank; //id of the process that host values to be exposed to all processes
int rank; //process id
int size; //number of processes
int *data;
int data_size;
};
struct mpi_vector_t *create_vec(int hostrank, std::vector<int> v) {
struct mpi_vector_t *vec;
vec = (struct mpi_vector_t *)malloc(sizeof(struct mpi_vector_t));
vec->hostrank = hostrank;
vec->data_size = v.size();
MPI_Comm_rank(MPI_COMM_WORLD, &(vec->rank));
MPI_Comm_size(MPI_COMM_WORLD, &(vec->size));
if (vec->rank == hostrank) {
MPI_Alloc_mem(vec->data_size * sizeof(int), MPI_INFO_NULL, &(vec->data));
for (int i=0; i<vec->size; i++) vec->data[i] = v[i];
MPI_Win_create(vec->data, vec->data_size * sizeof(int), sizeof(int),
MPI_INFO_NULL, MPI_COMM_WORLD, &(vec->win));
}
else {
vec->data = NULL;
vec->data_size = v.size();
MPI_Win_create(vec->data, 0, 1,
MPI_INFO_NULL, MPI_COMM_WORLD, &(vec->win));
}
return vec;
}
void delete_vec(struct mpi_vector_t **count) {
if ((*count)->rank == (*count)->hostrank) {
MPI_Free_mem((*count)->data);
}
MPI_Win_free(&((*count)->win));
free((*count));
*count = NULL;
return;
}
std::vector<int> get_vec(struct mpi_vector_t *vec) {
vector<int> ret(vec->data_size);
MPI_Win_lock(MPI_LOCK_SHARED, vec->hostrank, 0, vec->win);
MPI_Get(&ret.front(), vec->data_size, MPI_INT, vec->hostrank, 0, vec->data_size, MPI_INT, vec->win);
MPI_Win_unlock(0, vec->win);
return ret;
}
void put_vec(struct mpi_vector_t *vec, std::vector<int> v) {
MPI_Win_lock(MPI_LOCK_EXCLUSIVE, vec->hostrank, 0, vec->win);
MPI_Put(&v.front(), vec->data_size, MPI_INT, vec->hostrank, 0, vec->data_size, MPI_INT, vec->win);
MPI_Win_unlock(0, vec->win);
}
void print_vec(struct mpi_vector_t *vec) {
if (vec->rank == vec->hostrank) {
for (int i=0; i<vec->data_size; i++) {
printf("%2d ", vec->data[i]);
}
puts("");
}
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
struct mpi_vector_t *vec;
int rank;
vector<int> v = {2,3,1};
vec = create_vec(0, v);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
for (int itest = 0; itest < 2; itest++) {
vector<int> v1 = { rank, rank + 1, rank + 2 }; //some generated data
v = get_vec(vec);
if (v1[0] < v[0]) {
cout << "#" << rank << " assigns v1 {" << v1[0] <<
" ...} to host v {" << v[0] << " ...}" << endl;
put_vec(vec, v1);
}
}
MPI_Barrier(MPI_COMM_WORLD);
print_vec(vec);
delete_vec(&vec);
MPI_Finalize();
return 0;
}
答案 0 :(得分:2)
这是一个经典的数据竞赛场景。 get_vec
和put_vec
都单独锁定窗口,但实际上需要的是一个跨越整个代码块的锁,即:
lock_window();
v = get_vec(vec);
if (v1[0] < v[0])
put_vec(vec,v1);
unlock_window();
目前的情况是,共享向量的内容可能会在调用get_vec()
之后立即更改,因为另一个进程已执行put_vec()
并且可能使比较结果无效。像这样:
std::vector<int> compare_swap_vec(struct mpi_vector_t *vec, std::vector v) {
vector<int> ret(vec->data_size);
MPI_Win_lock(MPI_LOCK_EXCLUSIVE, vec->hostrank, 0, vec->win_ext);
ret = get_vec(vec);
if (v[0] < ret[0])
put_vec(vec, v);
MPI_Win_unlock(0, vec->win_ext);
return ret;
}
函数compare_swap_vec()
采用向量并使用它来替换共享向量的旧内容(如果小于关系成立)。它还返回向量的先前内容。 vec->win_ext
是由托管矢量内容的同一进程托管的另一个窗口。它用于外部锁定,因为MPI标准要求同一进程中同一窗口的不同访问时期必须是不相交的,我将其解释为不允许在同一窗口上嵌套锁定。