为什么在intel cpu上对锁定内存的访问不对齐读取中间值?

问题描述 投票:0回答:1

我现在正在研究未对齐的内存访问。

根据intel文档,Lock指令使用总线锁定跨两个缓存线的内存。 (我有一个“酷睿 i7”CPU。)

9.1.2 总线锁定

Intel 64 和 IA-32 处理器提供 LOCK# 信号,该信号在某些关键内存操作期间自动置位,以锁定系统总线或等效链路。该信号的置位称为总线锁定。当该输出信号被置位时,来自其他处理器或总线代理的总线控制请求将被阻止。软件可以通过在指令前添加 LOCK 前缀来指定要遵循 LOCK 语义的其他场合。

9.1.2.2 软件控制总线锁定

总线锁的完整性不受内存字段对齐的影响。 LOCK 语义遵循更新整个操作数所需的多个总线周期。

锁定操作相对于所有其他内存操作和所有外部可见事件来说是原子的。只有取指令和页表访问才能通过锁定指令。锁定指令可用于同步一个处理器写入的数据和另一处理器读取的数据。

对于 P6 系列处理器,锁定操作会序列化所有未完成的加载和存储操作(即等待它们完成)。这一规则也适用于 Pentium 4 和 Intel Xeon 处理器,但有一个例外。引用弱有序内存类型(例如 WC 内存类型)的加载操作可能无法序列化。

我做了如下多线程测试代码,结果并没有达到我的预期。

我希望使用InterlockedCompareExchange(使用Lock cmpxchg指令)使总线锁定,因此读取值(使用mov指令)也必须被序列化。但它读取的是中间值,例如 0xFFFFFFFFAAAAAAAAA 和 0xAAAAAAAAFFFFFFFF。

你能解释一下为什么吗?我是否误解了巴士锁或其他什么?

【测试总结】 读取和写入跨两个缓存行的内存地址。

线程1:比较并交换到NUM1(0xAAAAAAAAAAAAAAAAA)

线程2:比较并交换NUM2(0xFFFFFFFFFFFFFFFF)

thread3:读取并检查值是否不是NUM1和NUM2。

结果:

constexpr unsigned __int64 NUM1 = 0xAAAAAAAAAAAAAAAA;
constexpr unsigned __int64 NUM2 = 0xFFFFFFFFFFFFFFFF;

inline bool IsGarbageValue(unsigned __int64 value)
{
    return (value != NUM1 && value != NUM2);
}

#pragma pack(push, 1)
struct alignas(64) TestValue
{
    char pad[60];
    unsigned __int64 value;
};
#pragma pack(pop)


std::thread t1([&test]()
{
    while (1)
    {
        auto org = InterlockedCompareExchange(&test.value, NUM1, NUM2);
    }
});

std::thread t2([&test]()
{
    while (1)
    {
        auto org = InterlockedCompareExchange(&test.value, NUM2, NUM1);
    }
});

std::thread t3([&test]()
{
    while (1)
    {
        if (::IsGarbageValue(test.value))
        {
            std::cout << test.value << std::endl;
        }
    }
});
c++ multithreading atomic intel memory-alignment
1个回答
0
投票

auto org = InterlockedCompareExchange(&test.value, NUMa, NUMb)
读写值,保证读写操作的原子性。

但是,

if (::IsGarbageValue(test.value))
并不能保证读操作的原子性。

所以,你的

t1
t2
工作得很好,但你的
t3
只是读垃圾。

© www.soinside.com 2019 - 2024. All rights reserved.