gpt4 book ai didi

c++ - 与 memcpy 的数据竞争,未定义的行为?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:33:40 28 4
gpt4 key购买 nike

我在一个线程中写入内存区域(使用 memcpy),然后在另一个线程中使用 memcpy 将其复制到新位置。有时这些操作可能会重叠,从而导致数据竞争。具有数据竞争的程序会调用未定义的行为并且是无效的。

在这种情况下,我在复制后检查复制的数据是否有效(实际上没有发生竞争。)如果确实发生了竞争,我将丢弃复制的数据。但是,据我所知,这并没有让我摆脱关于 UB 的困境。我认为是否使用数据竞赛的结果仍然是 UB。

现在我可以在汇编中编写自己的 memcpy 例程(或者只是复制并粘贴 libc 中的例程),这将避开整个 UB 问题。汇编不是 C++,汇编中发生的任何事情都不会授予编译器调用鼻恶魔[1] 的许可。顺便说一句,对于内联汇编以及外部编译和链接的汇编来说都是如此吗?虽然 memcpy 已经在任何现代 libc 中汇编,但它也可以由编译器进行特殊处理,编译器通常会像小型内联 memcpy 一样针对已知大小和对齐方式进行优化 - 这可能会再次召唤鼻魔。

我是不是想多了?很难想象一个编译器如此神乎其神以至于它可以在编译时检测到数据竞争——同时又如此愚蠢以至于优化器使用它来生成错误代码而不是报告它。但是编译器最近有办法突破这两个限制 - 所以我觉得有必要在 Stack Overflow 上寻求建议。

[编辑] 由于很多人对我如何在此处同步事物感到好奇,所以让我解释一下。指向正在复制的内存的指针在线程之间共享。它通过原子 load(mo_acquire) 访问。然后将内存复制到新位置。然后是 LoadLoad barrier,然后是指针的第二个 load(mo_relaxed)。如果指针不匹配,则复制的结果将被丢弃,因为另一个线程可能在复制期间与该线程竞争。写入内存的线程首先使用原子 store(mo_relaxed) 更新指向 null 的指针,然后是 StoreStore barrier 和 racing memcpy。因此,虽然在不同线程中对 memcpy 的两次调用可能是数据竞争 - 实际上,这种情况总是会被检测到,并且在这种情况下结果总是会被丢弃。我将此方案称为读时复制,并使用它允许在对象被逐出之后但在内存被重新使用之前在缓存中复活对象,而不涉及任何互斥锁或“强”同步。

[1]:我渴望一个更文明的时代,编译器报告 UB 而不是滥用它进行可能与程序员期望的行为相反的优化。

最佳答案

同步锁使用的方法与您正在做的非常相似,尽管只占用非常小的内存。如果数据争用发生率高,同步锁会更快,但如果争用率低,您的方法实际上可能更快。

虽然memcpy的结果是undefined,但这不是undefined behavior,只要你能检测到是否发生了竞争,并且知道是否忽略垃圾结果。

这听起来不像您冒着违反保护或类似崩溃错误的风险;我使用 memcpy 的次数还不够多,无法知道在重叠操作期间是否存在任何可能崩溃的情况,但我认为它不应该。

因此,只要可以检测到行为,这就不一定是坏事,只要它以明显优于标准方法的方式满足您的需求。我不建议“仅仅因为”使用此方法,但是如果您需要使用传统锁无法获得的速度,并且您以通常提供文档的任何方式非常彻底地记录了定义明确但非标准的行为对于维护,这是可以接受的。

至于编译器优化评论,我从未见过编译器依赖未定义的行为来优化代码,并且由于 C++ 编译器需要根据 C++ 规范保证特定行为,我会立即停止使用任何依赖于未定义行为的编译器为此目的的未定义行为。库代码专门记​​录了不支持且不应执行跨线程同时读/写操作,因此以这种方式跨线程使用库代码不属于未定义行为,而是您自己故意滥用库代码风险,所有明示或暗示的保证均无效。

关于c++ - 与 memcpy 的数据竞争,未定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35713190/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com