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

时间:2016-03-01 01:42:05

标签: c++ multithreading undefined-behavior

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

在这种情况下,我会在复制后检查复制的数据是否有效(实际上没有发生竞赛。)如果确实发生了竞争,我会丢弃复制的数据。然而,AFAIK,并没有让我摆脱UB的困扰。我认为无论我是否使用数据竞赛的结果,它仍然是UB。

现在我可以在程序集中编写我自己的memcpy例程(或者只是从libc复制并粘贴一个例程),这将会影响整个UB问题。程序集不是C ++,在程序集中发生的任何事情都不会给编译器许可证调用鼻子 [1] 。顺便提一下,对于内联asm以及外部编译和链接的asm是真的吗?尽管memcpy已经在任何现代libc中组装,但编译器也可以对其进行特殊处理,编译器通常会针对已知大小和对齐进行优化,如小内联memcpy - 这可能会再次调用鼻子恶魔

我在这里过分思考?很难想象编译器如此神似,以至于它可以在编译时检测数据竞争 - 同时也是如此愚蠢以至于优化器使用它来生成错误的代码而不是报告它。但编译器最近有办法推动这两个限制 - 所以我觉得有必要在Stack Overflow上寻求建议。

[编辑]由于我对如何同步事物有很多好奇,让我解释一下。正在复制的内存的指针在线程之间共享。它使用原子load(mo_acquire)进行访问。然后将内存复制到新位置。然后是LoadLoad barrier,然后是指针的第二个load(mo_relaxed)。如果指针不匹配,则复制的结果将被丢弃,因为另一个线程可能在复制期间与此线程竞争。写入内存的线程首先将指针更新为null,原子为store(mo_relaxed),后跟StoreStore barrier和赛车memcpy。因此,虽然在不同线程中对memcpy的两次调用可能是数据争用 - 实际上总是检测到并且在这种情况下结果总是被丢弃。我将这个方案称为copy-on-read,并且我使用它来允许在它们被驱逐之后但在重新使用内存之前在没有任何互斥锁或强大的#34;之前恢复缓存中的对象。涉及同步。

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

3 个答案:

答案 0 :(得分:2)

你大多是正确的。仅仅因为一个执行线程看到复制的数据是"有效",它并不意味着另一个执行线程会看到同样的事情。

为了让其他执行线程看到某些操作的效果,无论是memcpy()还是其他任何操作,其他执行线程必须进行排序"有问题的操作。

这只是一个粗略,不精确的总结。排序中有大量墨水溢出。它不是一个简单的主题,有很多选项和规则。

但是胶囊摘要是实现线程安全和线程一致行为的最简单方法是使用互斥锁来保护用于在线程之间传递数据块的内存的共享区域。只要每个线程获取互斥锁,在访问共享内存区域进行读取或写入之前,所有线程都将是一个幸福的家庭。

答案 1 :(得分:2)

同步锁使用与您正在执行的操作非常相似的方法,但仅限于非常少量的内存。如果数据发生率很高,同步锁定会更快,但如果竞争率很低,你的方法可能会更快。

虽然memcpy的结果未定义,但这不是未定义的行为,只要您可以检测是否发生了竞争,并知道是否忽略垃圾结果。

这听起来好像存在保护违规或类似崩溃错误的风险;我没有使用memcpy足以知道在重叠操作期间是否有任何可能崩溃的情况,但我不相信它应该。

因此,只要能够检测到行为,如果它以比标准方法明显更好的方式满足您的需求,那么这不一定是坏事。我不建议使用这个"只是因为",但如果你需要速度,你不能使用传统的锁,你记录明确但非标准的行为无论你通常提供维护文件的方式都非常彻底,这是可以接受的。

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

答案 2 :(得分:0)

使用其中一个新的C ++ mutex对象来同步对共享内存或其他同步机制的访问。