请考虑以下事项:
ThreadA和ThreadB是将诊断信息写入公共对象的两个线程,该公共对象存储诊断信息列表。 ThreadA和ThreadB是否可以同时写入相同的内存地址?如果是这样会导致什么结果?
我正在使用.NET但是我不一定对某个特定语言的答案感兴趣。
答案 0 :(得分:5)
“同一时间”只能达到一定的粒度 - 在某些时候,对存储器阵列的实际写入将被序列化。那时,该地址的值将是最近发生的任何写入的值。你可以在多处理器系统上有一些有趣的行为。如果每个线程在不同的处理器上运行,每个处理器都有自己的缓存,那么每个线程可能只看到自己写入的结果,甚至从未知道其他线程的尝试。
答案 1 :(得分:5)
<强>腐败强>
无论系统[并发还是真正并行],存储器的状态都取决于存储器设备的实现。一般来说,内存读取和写入不是原子的,这意味着对同一内存地址的多次并发访问可能会返回不一致的结果[即数据损坏]。
想象一下两个并发请求,一次写入,一次读取,一个简单的整数值。假设一个整数是4个字节。我们还要说,读取需要2ns才能执行,写入需要4ns才能执行
read返回的值既不是原始值也不是新值。该值已完全损坏。
这对你意味着什么!
不可否认,这是一个令人难以置信的简化和人为的例子,但这可能会对你的场景产生什么影响呢?在我看来,诊断系统中最容易受到攻击的是诊断数据列表。
如果你的列表是固定大小的,比如一个对象引用数组,最好你可能丢失整个对象,因为数组元素被竞争线程覆盖,最坏的情况是如果元素包含一个损坏的对象引用则会出现错误[a la腐败场景]。
如果您的列表是动态的,则可能会损坏底层数据结构[如果在重新分配时在.Net List<>
中的数组,或者您的下一个\ prev引用丢失的链接列表或者已腐败]。
暂且放弃
为什么内存访问不是原子的?出于同样的原因,基本集合实现不是原子的 - 它会限制太多并引入开销,从而有效地惩罚了简单的场景。因此,消费者[我们!]需要同步我们自己的内存访问。
答案 2 :(得分:0)
在您的问题中,您指的是对象和内存位置。我将假设您的意思是内存位置,因为对象可能会以不同的方式执行操作(分配新内存或其他内容)。您可以让两个线程写入相同的地址,但结果是不可预测的。如果执行两个线程并在调用后查看结果,则不能依赖于某个结果。如果需要这样做,则必须使用互斥锁或临界区等保护写入操作。如果它是一个对象,该对象可能会保护自己是线程安全的。很难说没有更多细节...
答案 3 :(得分:0)
主要是应用程序崩溃或数据损坏。想象一下来自两个不同线程的混合原子操作。
答案 4 :(得分:0)
硬件设计无法同时写入相同的全局内存地址! 可能是虚拟写入,以防CPU的任何核心具有自己的缓存,但结果是不可预测的,从那种情况发明了“LOCK”指令,该指令被授予无阻碍或原子访问全局存储器地址。
答案 5 :(得分:0)
这里有一些非常好的答案可以解释多个线程写入共享内存地址会出现什么问题,所以我不会重新审视这些内容。
为了防范这些问题,我们开发了一些非常有用的机制来通过设计创建健壮的多线程系统。
当多个线程需要将数据提交到公共目标时,使用OS队列非常有效(并且通常是语言不可知)(或者如果在您的操作系统上没有,请使用其写入受其保护的管道)互斥)。在读取队列时,可以阻止另一个线程,该队列以非常同步的方式处理数据。
只要队列深度(或管道缓冲区)足够大以防止写入阻塞,线程就不会遭受性能损失,除了锁定和释放互斥锁的轻微开销。