我有一个并行化的例程,可以对大型指针数组中的每个对象执行一系列计算,其中计算要求每个线程都能够读取所有其他对象,但只能写入一个对象。我将其设置如下所示
#include <atomic>
#include <thread>
void threadFunction(Object** objects, int n);
std::atomic<int> idx;
int nobjects = 10000;
int main() {
int nthreads = 4;
Object** objects = new Object*[nobjects];
idx = 0;
std::thread threads[nthreads];
for (int ii = 0; ii < nthreads; ii ++) {
threads[ii] = std::thread(threadFunction, objects, ii);
}
while (idx < nobjects - 1) {} // Wait until all the calculations have been done
for (int ii = 0; ii < nthreads; ii ++) {
threads[ii].join();
}
}
void threadFunction(Object** objects, int n) {
Object* current = NULL;
while (idx < nobjects - 1) {
current = objects[idx++];
// do calculation
}
}
其中Object
是一个自定义类,但可以出于这些目的用其替代原语。我的问题是这种方式的“安全性”如何?我知道atomic
类型受到保护,可以部分写入,但是我可以确定这种方法每次都适用于大型数组吗?
这可能太具体了,但我将不胜感激。谢谢
答案 0 :(得分:3)
正如其他人在评论中指出的那样,您在检查循环条件和使用idx
的值之间有一个竞争条件。这可能会导致您读取数组末尾的内容。您的线程功能只需要稍作调整:
void threadFunction(Object** objects, int n) {
Object* current = NULL;
while (true) {
int next = idx++;
if (next < nobjects - 1) {
current = objects[next];
// do calculation
} else {
break;
}
}
}
通常,很难证明无锁算法是正确的,并且只能通过仔细检查代码来完成。在任何情况下,数组的大小都与该算法的正确性无关。
使用标准库
尽管没有特别要求,但是可能值得指出的是,所有这些都可以通过标准库完成(它避免了棘手的安全性问题并处理了分区之类的问题)。类似于以下内容:
void DoCalculations(Object& obj)
{
// details...
}
// later...
std::vector<std::unique_ptr<Object>> objects = CreateObjects();
std::for_each(
std::execution::par,
objects.begin(),
objects.end(),
[] (std::unique_ptr<Object> const& p) { DoCalculations(*p); });