debugging 为什么在我的代码cpp compare_exchange_strong中更新并返回false

sg24os4d  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(164)

问题:

所以我对CPP还是个新手,我试图使用一些原子性概念来实现一个简单的比较代码。
问题是我没有得到一个想要的结果,那就是:即使在compare_exchange_strong函数更新原子变量(std::atomic)的值之后,它也返回false。
下面是程序代码:

CPP:
Action::Action(Type type, Transfer *transfer)
: transfer(transfer),
  type(type) {
    Internal = 0;
    InternalHigh = -1;
    Offset = OffsetHigh = 0;
    hEvent = NULL;
    status = Action::Status::PENDING;
}

BOOL CancelTimeout(OnTimeoutCallback* rt)
{
    auto expected = App::Action::Status::PENDING;

    if (rt->action->status.compare_exchange_strong(expected, App::Action::Status::CANCEL)) 
    {
        CancelWaitableTimer(rt->hTimer);

        return true;
    }

    return false;
}
标题:
struct Action : OVERLAPPED {
    enum class Type : long {
        SEND,
        RECEIVE
    };

    enum class Status : long {
        PENDING,
        CANCEL,
        TIMEOUT
    };
    
    atomic<Status> status;
    Transfer *transfer = NULL;
    Type type;
    WSABUF *data = NULL;
    OnTimeoutCallback *timeoutCallback;

    Action(Type type, Transfer *transfer);

    ~Action();
}

回顾一下,变量rt->action->status的值被更新为Action::Status::CANCEL枚举,但是compare_exchange_strong函数的返回值为false。
查看调试中的问题:

也就是说,考虑到第一个断点更改了变量的值,所需的结果是触发第一个断点(引用return true)而不是return false。
更新:在打印中,我无意中删除了第一个断点,但我认为这是可以理解的

已尝试

1.将结构修改为:enum class Status : long
1.将结构修改为:enum class Status : size_t
1.修改所有结构项目的位置

已搜索过类似主题

[但没有成功]

| 连接|检索词|
| - -|- -|
| Why does compare_exchange_strong fail with std::atomic, std::atomic in C++?|比较交换强失败|
| cpp compare_exchange_strong fails spuriously?|比较交换失败|
| Don't really get the logic of std::atomic::compare_exchange_weak and compare_exchange_strong|标准::原子::比较弱交换和比较强交换|
| C++14是否定义了无符号整型的填充位上的按位运算符的行为?|填充问题比较交换|
在其他几个主题中使用不同的搜索词

重要注解

1.代码是多线程的
1.在代码中没有其他地方将原子变量的值更新为枚举Action::Status::CANCEL
1.我怀疑这与填充有关(由于一些Google搜索),但是由于我是CPP的新手,我不知道如何修改我的框架来解决这个问题
1.每个请求都会生成Action结构的一个新示例,而且我还确保在同一指针(Action*)上不会发生并发,因为每次更改都会生成Action结构的一个新示例
等一下!
值得一提的是,我是用谷歌翻译发布这个问题,以防有什么不对的地方,如果我的问题不完整,或者格式不合适,请发表评论,这样我就可以调整它,提前谢谢,
卢卡斯山口

更新:

我无法使用代码的缩小版本来复制问题,也就是说,我必须发布整个解决方案(这本身已经很小了,因为它是一个研究项目):
https://drive.google.com/file/d/13fP7OUCC6GeMgUtrPHSOnSGUEBwDGqBC/view?usp=sharing

qf9go6mv

qf9go6mv1#

TL:DR:修改它的另一个执行绪与取得控制权并阅读所两柴之行程序内存的调试工具之间的竞争条件。

或者值长时间为Action::Status::CANCEL,而不是expected = App::Action::Status::PENDING;,在这种情况下,单独运行的单个线程可能会有这种行为。我假设您的程序预期此CAS仅在两个线程几乎同时尝试执行此操作时才会失败,例如只有在有挂起的事情时才首先调用此函数。

我假设有另一个线程可以同时调用CancelTimeout,否则您不需要原子RMW。(如果这是唯一修改它的线程,您只需检查值,并在手动比较后对新值进行纯存储,如.store(CANCEL),可能使用std::memory_order_release或relaxed。)

这可以解释你的观察结果:

  • 另一个线程赢得了修改rt->action->status的竞争,因此其CAS返回true。
  • 此线程中的CAS_strong * 未 * 修改变量,返回false。
  • 此线程中的if主体未运行,因此此线程遇到断点。
  • 在调试器最终获得控制权并且进程的所有线程都被暂停之后,调试器要求内核读取正在调试的进程的内存。由于我们的CAS失败,另一个线程对rt->action->status的更新肯定已经发生,因此调试器将看到它。

(特别是在调试器获得控制权花费了这么长时间之后,这些灰尘将有时间沉淀下来。但是假设您使用的是x86或ARMv 8,一个线程中的存储对任何其他线程可见意味着它们对所有线程都是全局可见的;这些ISA是多拷贝原子,no IRIW reordering。)

**因此CAS失败的原因是其他线程 * 已经 * 更改了该值。CAS失败的线程没有更改该值。**无论CAS之前或之后的值是什么,只要CAS失败,就会触发断点。

要让CAS_strong返回false并更新值,您的编译器或CPU必须有bug。这些都是 * 可能的 *(尤其是编译器bug),但这些都是非同寻常的说法,需要非常仔细地排除相同观察结果的软件原因。当您还没有整理好所有细节,也不确定您是否理解正在发生的一切时,这不应该是您的第一个猜测。

如果您认为一个原始操作没有做到文档所说的那样,那么它几乎总是在其他地方出现了bug,或者缺少了一些可能的解释,而这些解释不需要编译器bug来解释。

问一个关于堆栈溢出的问题是可以的,但是在写标题的时候要记住,你的C++编译器实际上是不可能坏的。

相关问题