C 为什么这种竞态条件只发生在-O3中,并且只发生在某个看似等价的代码顺序中?

C 为什么这种竞态条件只发生在-O3中,并且只发生在某个看似等价的代码顺序中?,c,optimization,race-condition,reference-counting,C,Optimization,Race Condition,Reference Counting,我已经实现了一个不可变的哈希映射和附带的STM容器,其灵感来自clojure的atom,也就是说,类似于C++的std::unique_ptr,它通过一个指针管理(但不一定拥有)另一个对象,该指针可以传递和交换,并且只减少托管对象的引用计数,而不是彻底销毁它。当运行一些用-O3编译的测试代码时,我开始注意到错误的结果 比较和交换的代码如下所示: hashmap*update(stm*stm,update\u回调cb,void*user\u参数) { 而(1){ 锁(stm->mutex); ha

我已经实现了一个不可变的哈希映射和附带的STM容器,其灵感来自clojure的
atom
,也就是说,类似于C++的
std::unique_ptr
,它通过一个指针管理(但不一定拥有)另一个对象,该指针可以传递和交换,并且只减少托管对象的引用计数,而不是彻底销毁它。当运行一些用
-O3
编译的测试代码时,我开始注意到错误的结果

比较和交换的代码如下所示:

hashmap*update(stm*stm,update\u回调cb,void*user\u参数)
{
而(1){
锁(stm->mutex);
hashmap*current=stm->reference;
增量_ref_count(当前);//第6行
解锁(stm->mutex);
hashmap*aspirant=cb(当前,用户参数);//返回一个新的线程本地实例
增量参考计数(有志者);
//位置1
锁(stm->mutex);
如果(current==stm->reference){//success,在我们忙的时候没有修改
stm->reference=aspirant;
increment_ref_count(aspirant);//stm现在有一个引用
解锁(stm->mutex);
//立场2.1
递减_ref_count(当前);//在第6行获取的释放引用
递减_ref_count(当前);//stm不再有引用
回归上进者;
}否则{//引用已修改,请循环并重试
解锁(stm->mutex);
//立场2.2
递减_ref_count(当前);//在第6行获取的释放引用
减量\u ref\u计数(aspirant);//ref\u计数现在为零,aspirant自由了
}
}
}
递增
递减
中的参考计数-/原子地减少hashmap的参考计数。如果计数由于递减而降为零,hasmap不久后将被释放

为引用计数指针设计STM容器的挑战主要是获取引用和增加计数器原子,这就是我在这里使用锁的原因

作为测试,我使用hashmap+STM来计算单词列表中的出现次数

如果我按照这里发布的代码运行代码,则不会出现竞争条件现在我的问题来了:如果我移动
减量\u ref\u count(当前);//对于
if/else
中的第6行
,远离
位置2.1/2.2
(位于第二个锁定区域的正后方),并将其放置在
位置1
(位于第二个锁定区域的正前方),突然字数开始不正确,我不知道为什么

我的论点是:a)我在第二个关键区域不使用
current
,b)因此,在比较和交换之前或之后释放引用并不重要

显然,我有一个解决问题的方法;只需将
减量保持在现在的位置,但我真的很想了解为什么会发生这种情况


在“Linux Windows子系统”上编译为:
gcc-Wall-Wcast align-Wswitch enum-Wswitch default-Winit self-pedantic-O3-DNDEBUG-std=gnu11

我发现了问题,甚至吸取了“教训”。首先,让我重新发布代码的损坏版本,以便更容易地讨论:

 1 | hashmap *update(stm *stm, update_callback cb, void *user_args)
 2 | {
 3 |     while (1) {
 4 |         lock(stm->mutex);
 5 |         hashmap *current = stm->reference;
 6 |         increment_ref_count(current); // Line 6
 7 |         unlock(stm->mutex);
 8 | 
 9 |         hashmap *aspirant = cb(current, user_args); // returns a new, thread local instance
10 |         increment_ref_count(aspirant);
11 | 
12 |         decrement_ref_count(current); // current might get free'd here
13 | 
14 |         lock(stm->mutex);
15 |         if (current == stm->reference) { // success, no modification while we were busy
16 |             stm->reference = aspirant;
17 |             increment_ref_count(aspirant); // stm now has a reference
18 |             unlock(stm->mutex);
19 | 
20 |
21 |
22 | 
23 |             decrement_ref_count(current); // stm no longer has a reference
24 |             return aspirant;
25 | 
26 |         } else { // reference was modified, loop and try again
27 |             unlock(stm->mutex);
28 | 
29 |
30 |
31 | 
32 |             decrement_ref_count(aspirant); // ref_count now at zero, aspirant is free'd
33 |         }
34 |     }
35 | }
问题基本上是,在第15行我使用了一个地址,我已经放弃了对它的控制。我假设,如果另一个线程要修改
stm->reference
,它将使用不同的地址进行修改。相反,发生了以下情况:当我的线程在第14行等待锁时,
current
散列映射被解除分配,一个新的散列映射被分配到相同的内存地址,并成功地存储到
stm->reference
。然后我的线程将继续,发现
current==stm->reference
,错误地得出结论,自获取引用并执行交换后,
stm->reference
没有发生任何更改,因此丢失了一些更新


这里的教训是:永远不要使用
免费的指针,即使只是用来比较地址

这似乎缺少更多的上下文。什么是
current
?@tadman
current
stm
当前指向的哈希映射实例。如果此函数中当前没有线程(
update
),并且没有线程获取用于读取的引用,则其引用计数为1,因为
stm
是指向该哈希映射实例的唯一对象。我不精通C++,但是我所说的“STM”与线程安全<代码> STD::UnQuyJPPT类似。当你在代码中某个地方有不明确的行为时,随机代码突然中断或在你四处乱说各种语句时会发生。通常与您移动的代码无关。或者是堆栈溢出,这在这里看起来不太可能。但是你在这里发布的只是一堆函数调用——要找到bug,你需要深入挖掘那些函数,或者,你正在使用。这是哪一个编译器?@Lundin-Oh-boy。。。你对我从哪里或如何开始寻找UB有什么建议吗?我使用大量的
-W
标志(请参见更新的问题)和gcc进行编译,结果是干净的。