1. MEMORY_BARRIER的正确用法
为了更好的说明问题,这里只讨论读写内存栅栏,关于读内存栅栏、写内存栅栏可以看我发的文档。 以下为简化的伪代码,这是唯一正确的MEMORY_BARRIER用法:
//生产者(对应原来的PUSH接口):
PUT(data)
{
if (writeCusor + 1 != readCusor)
{
dataBuffer[writeCusor] = data;
MEMORY_BARRIER();
writeCusor++;
}
}
//消费者(对应原来的POP接口):
GET(data)
{
if (readCusor != writeCusor)
{
data = dataBuffer[readCusor];
MEMORY_BARRIER();
readCusor++;
}
}
2. MEMORY_BARRIER的语义
MEMORY_BARRIER原语的语义为: 保证MEMORY_BARRIER之前的所有读写操作都在MEMORY_BARRIER之后的指令之前完成,并且所有CPU核看到都是这样的顺序。
2. 5G L2的代码存在的问题
5G L2的代码中,GET(data)接口的MEMORY_BARRIER()位置是这样的:
GET(data)
{
if (readCusor != writeCusor)
{
MEMORY_BARRIER();
data = dataBuffer[readCusor];
readCusor++;
}
}
这里假定(1)语句中的readCusor变量受(2)语句中的加加操作影响,编译器和CPU会自动识别,不会对这两行代码做优化、调整,会严格按照字面顺序执行。
(1) data = dataBuffer[readCusor];
(2) readCusor++;
这样的假定是存在问题的,因为编译器或CPU可能会“优化”成下面这样:
GET(data)
{
if (readCusor != writeCusor)
{
MEMORY_BARRIER();
readCusorTemp = readCusor;
readCusor++;
data = dataBuffer[readCusorTemp];
}
}
这样在生产者PUT那边会先看到readCusor++,会在队列满的情况下,概率出现GET数据被覆盖的情况。 虽然概率低,但一出问题就是严重甚至致命问题。