【发布时间】:2015-03-31 15:19:54
【问题描述】:
我想我发现 Helgrind 工具返回的误报类型相当广泛。也许这已在其他地方记录,但似乎 Helgrind 工具总是会错误地将Test and Test-And-Set pattern 检测为误报。
struct resource {
int in_use;
int value;
pthread_mutex_t lock;
}
// assume each member of resource is initialized in the main function
// in_use is initialized to zero
// value is initialized to zero
// and the lock is initialized with pthread_mutex_init()
struct resource[1000];
void insertIntoUnused(int toInsert) {
int i;
for (i = 0; i < 1000; i++) {
if (resource[i].in_use == 0) {
pthread_mutex_lock(&resource[i].lock);
if (resource[i].in_use == 0) {
resource[i].in_use = 1;
resource[i].value = toInsert;
pthread_mutex_unlock(&resource[i].lock);
return;
}
pthread_mutex_unlock(&resource[i].lock);
}
}
}
如果许多线程运行上述insertIntoUnused 函数,Helgrind 将在第一次读取in_use 变量时检测到可能的竞争条件。但是,in_use 变量在获取锁后会再次检查,而in_use 变量仅在获取锁时写入。
TTAS 模式是一种非常常见的减少锁竞争的方法。我很惊讶 Helgrind 不能“支持”这种模式。
我了解 Helgrind 算法为何会在此处检测竞争条件。每次读取in_use 变量和每次写入之间都没有happens-before 关系。然而证明上面的代码是正确的并不难(我实际上并没有把它打出来……所以,除了错别字,它在文献中已经很好地确立了)。
如何让 Helgrind 停止考虑第一次检查潜在的竞争条件,以便我可以在我的程序中找到其他竞争条件?
【问题讨论】:
-
“然而证明并不难”实际上我想看看证明,考虑到代码确实被破坏了;)这几乎是双重检查锁定模式数组,并且在文献中很好地证明了它是如何被破坏的。 E.g. this here 适用于 java,但同样适用于 c++。您的 wiki 链接正在讨论一条 cpu 指令,如果您不介意线程看到未初始化的变量(我对此表示怀疑),该指令仅在此处有用(我对此表示怀疑)
-
有趣。我没有想到 Java 案例或 C++ 类变量案例。但是,您链接的站点似乎表示问题是类属性的分配可能发生在
Helper对象的构造函数完成之前。这很明显。 AFAIK,int 的“构造函数”是原子的,因此这不适用于 Helgrind 抱怨的情况。 -
我还应该注意,您链接的页面确认它也适用于 JVM 中的 32 位原语,因为这种大小的写入是原子的(显然)。
-
即使可以原子地完成的写入也不能保证按程序顺序完成(但这实际上不是这里的问题,因为在这种情况下程序会写入@987654331 @ 在
value之前,所以value的读者显然无论如何都必须获得锁。) -
@Ryan 问题很简单:假设T1先进入,直到
resource[0].in_use = 1;。这时 T2 进入看到resource[0].in_use == 1并愉快地开始使用仍然未初始化的resource[0].value。由于您一开始就错误地编写了代码,我们甚至不必对两次写入进行任何编译器/CPU 重新排序,但显然这些都是可能的。现在,如果你总是只写而不读,你可以使用 int(假设 C 标准不保证原子性),但我认为有人也必须阅读,然后我们就有问题了。
标签: c multithreading pthreads valgrind