【问题标题】:Valgrind / Helgrind falsely reporting TTAS pattern as raceValgrind / Helgrind 错误地将 TTAS 模式报告为种族
【发布时间】: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


【解决方案1】:

POSIX 线程模型表明,对resource.in_use 的非同步访问会导致未定义的行为,尽管在这种情况下它“看起来还不错”。

理论上,编译器可以利用这一点 - 例如,一旦循环的执行将 resource.in_use 视为非零,它就不需要再次测试它,因为(没有数据竞争)什么都没有在resource.in_use != 0 的情况下,可能会导致该值发生变化。 IE。它可以将其更改为以下内容:

for (i = 0; i < 1000; i++) {
    if (resource.in_use != 0) {
        i = 1000;
        break;
    }

    pthread_mutex_lock(&resource[i].lock);
    if (resource.in_use == 0) {
        resource.in_use = 1;
        resource.value = toInsert;
        pthread_mutex_unlock(&resource[i].lock);
        return;
    }
    pthread_mutex_unlock(&resource[i].lock);
}

【讨论】:

  • 啊,现在更有意义了。果然,我原来的帖子有错字。我忘了包括索引。因此,我认为您建议的特定重写不会发生。但我知道未定义的行为是未定义的行为。我必须仔细研究规范才能准确找到它。有链接吗?
猜你喜欢
  • 2021-04-10
  • 2012-05-25
  • 2013-02-07
  • 2011-07-01
  • 2016-08-28
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多