一. 数据竞争(data race)
在共享内存的情况下,如果两个线程同时对一个数据进行写操作,可能会导致数据竞争和不确定性问题。
比如线程1执行 x = 1, 线程2执行 x = 2。很难判断x的最终结果是什么,因为你不知道这两个线程谁先执行谁后执行。
如果在并行运算中,不同的线程出现了数据竞争的情况,或者两个线程之间有数据依赖关系,该如何解决呢?
example 1: 把变量私有化
这是一段简单的 数组a的元素 =(数组b的元素+数组c的元素)/ 2 的代码。在编写代码的过程中,大家习惯于使用一个temp变量存储临时结果。
float temp, a[n], b[n], c[n];
... // Initialise arrays b and c
int i;
for ( i =0; i < n; i++)
{
temp = 0.5f * (b[i] + c[i]);
a[i] = temp;
}
那么,在加入#pragma omp parallel for将for循环并行化以后
float temp, a[n], b[n], c[n];
... // Initialise arrays b and c
int i;
#pragma omp parallel for
for ( i =0; i < n; i++)
{
temp = 0.5f * (b[i] + c[i]);
a[i] = temp;
}
变量temp会被多个线程同时访问,造成数据竞争的状况。比如,线程A计算得到temp = 1以后,线程B计算得出temp = 2, 覆盖了A的结果,会导致线程A下的a[i]结果错误。
因此,需要将temp元素变为每个线程私有的。
float temp, a[n], b[n], c[n];
... // Initialise arrays b and c
int i;
#pragma omp parallel for private(temp)
for ( i =0; i < n; i++)
{
temp = 0.5f * (b[i] + c[i]);
a[i] = temp;
}
example 2: 提前备份数据
这是一段简单的将数组的每个元素前移一位的代码:
float a[n];
... // Initialise array a
int i;
#pragma omp parallel for
for ( i = 0; i < n-1; i++ )
a[i] = a[i+1];
在并行执行的情况下,可能线程A先执行,更改了数组的元素,导致线程B开始执行以后访问的数组已经是被A更改过的结果了。
解决方案是事先把原始数组备份:
float atemp[n];
#pragma omp parallel for
for (i = 1; i < n; i++)
atemp[i] = a[i];
#pragma omp parallel for
for (i = 0; i < n-1; i++)
a [i] = atemp[i+1];
example 3: 奇偶元素分开操作
这是一段简单的冒泡排序代码:
void sortSet() {
int i, j, temp;
for (i = 0; i < setSize-1; i++) {
for (j = 0; j < setSize-i-1; j++) {
if (set[j] > set[j+1]) {
temp = set[j];
set[j] = set[j+1];
set[j+1] = temp;
}
}
}
}
如果单纯在外层for循环加上#pragma omp parallel for来使其并行化的话,会出现排序结果混乱的问题。举例:
对数组[3,2,1]进行升序排序。
假设线程A判断set[0] > set[1],认为应该交换set[0] 和set[1]
线程B判断set[1] > set[2],认为应该交换,并率先进行,数组变成 [3,1,2]
A在B的基础上进行更改,交换set[0] 和set[1],数组变成 [1,3,2]
最后并没有达到理想的结果[1,2,3]。
解决方案:由于冒泡排序只有相邻的元素会互相交换,收到并行运算的影响,因此将其奇偶元素分开操作:
void sortSet()
{
int i, j;
int flag = 0;
for (i = 0; i < setSize - 1 && !flag; i++)
{
flag = 1;
#pragma omp parallel for
for (j = 1; j < setSize - 1; j += 2)
{
if (set[j] > set[j + 1])
{
int temp = set[j];
set[j] = set[j + 1];
set[j + 1] = temp;
flag = 0;
}
}
#pragma omp parallel for
for (j = 0; j < setSize - 1; j += 2)
{
if (set[j] > set[j + 1])
{
int temp = set[j];
set[j] = set[j + 1];
set[j + 1] = temp;
flag = 0;
}
}
}
}
其中flag的作用是标记是否在当前轮迭代中发生了交换。如果没有发生交换,则说明数组已经有序,此时可以提前结束排序。