根据我的阅读,英特尔处理器架构强制执行比 .net 实现所需提供的更强大的内存模型。代码在多大程度上利用英特尔处理器做出的保证是合适的,或者代码应该在多大程度上添加英特尔实现不需要的内存屏障,以防代码迁移到一个较弱的平台记忆模型?定义一个带有方法的静态类是否合适,例如“如果使用弱内存模型,则执行内存屏障”,并要求代码酌情与该库的“强模型”或“弱模型”版本链接?或者,是否可以在程序启动时使用反射来生成这样一个静态类,这样 JIT 编译器可以在使用强模型时,“
如果我有我的 druthers,.net 将提供MemoryLock
具有一些半锁操作的类的变体,这将要求所有持有半锁的线程都必须遵守该半锁的内存模型。在具有非常强大的内存模型的系统中,半锁不会做任何事情。在内存模型非常弱的系统中,任何希望进入已经有另一个线程的半锁的线程都必须等到第一个线程退出,或者它可以被 CPU 或内核调度(基于根据半锁指定的模型)第一个线程正在使用。请注意,与普通锁不同,aMemoryLock
永远不会死锁,因为任何冲突的锁定要求的组合都可以通过调度所有线程在同一个 CPU 上运行来解决,并且系统可以释放MemoryLock
由死掉的线程持有的任何线程(因为这样做的目的MemoryLock
是保护资源不被访问以违反内存模型的方式,死线程当然不能进行此类访问)。
当然,从 .net 4.0 开始就不存在这样的东西了;鉴于此,处理确实存在的情况的最佳方法是什么?将专为更强的内存模型设计的代码迁移到具有更弱模型的系统,在没有一些方法来强制执行更强的模型的情况下,将是灾难的根源,但是添加大量Lock
或MemoryBarrier
对于代码的原始目标平台而言,不必要的调用似乎不是很吸引人。我所知道的强制使用强内存模型的代码的唯一方法是让每个线程设置其 CPU 亲和性。如果有一种方法可以设置一个进程选项,这样 .net 一次只能使用一个内核,那可能会很有用(特别是如果它意味着 JIT 可以用更快的非总线锁定等价物代替总线锁定互锁操作) ,但我知道设置 CPU 亲和性的唯一方法是限制程序将特定选定的 CPU 用于其所有线程,即使该 CPU 被其他应用程序负载过重并且其他一些 CPU 处于空闲状态。
附录
考虑以下代码:
// 线程 1 -- 假设在开始时 SharedPerson 指向一个人 "Smiley", "George" var newPerson = new Person(); newPerson.LastName = "辛普森"; newPerson.FirstName = "巴特"; // 也许MemoryBarrier1 SharedPerson = newPerson; // 线程 2 var wasPerson = SharedPerson; // 也许MemoryBarrier2 var wasLastName = wasPerson.FirstName; var WasFirstName = wasPerson.LastName;
据我了解,即使没有内存屏障,在英特尔处理器上运行的代码也能保证写入不会被重新排序;因此,在线程 2 中,被读取的人要么是“Smiley”、“George”,要么是“Simpson”、“Bart”。然而,.net 内存模型比这更弱,.net 程序可能会发现自己运行在线程 2 可能会看到不完整对象的处理器上(因为写入SharedPerson
可能发生在写入之前newPerson.FirstName
)。在 at 添加内存屏障MaybeMemoryBarrier1
可以避免这种危险,但无论是否实际需要内存屏障,都会有性能成本。
我认为最低要求的 .net 内存模型不会那么弱,以至于在保证线程 2在读取自身之前MaybeMemoryBarrier2
永远不会访问所引用的对象的情况下需要(如上面代码中的情况,因为新实例在存储到 ) 之前不会暴露给任何外部代码。另一方面,假设情况略有变化,因此创建了一条记录,然后将其放入队列中(假设队列本身所有必要的锁和内存屏障);之后,处理器会:SharedPerson
SharedPerson
SharedPerson
Thread 2
JobInfo
Thread 1
// 线程 1 var newJob = JobQueue.GetJob(); // 获取 Thread2 写入的 JobInfo newJob.StartTime = DateTime.Now(); // 八字节结构可能跨越缓存行 // 一旦写入就永远不会改变 // 也许MemoryBarrier1 当前工作 = 新工作; // 线程 2 var wasJob = 当前工作; // 也许MemoryBarrier2 var wasStartTime = CurrentJob.StartTime();
如果线程 1 有内存屏障,但线程 2 没有,是否可以保证当线程 2 看到JobInfo
它创建的记录出现在 中时CurrentJob
,它会正确读取其StartTime
字段(并且不会看到缓存或部分缓存的值从Thread 2
操纵那个物体的时候开始?