CPU动态调换执行指令
以前,我从某些书籍上有看到编译器在优化代码的时候会改变C++代码的执行顺序;其实CPU为了优化执行的效率也可能会动态改变代码执行顺序。
以下内容来自《程序员的自我修养--链接、装载与库》
一段典型的double-check的singleton代码如下:
volatile T* pInst = 0; T* GetInstance() { if (pInst == NULL) { lock(); if (pInst == NULL) pInst = new T(); unlock(); } return pInst; }
抛开逻辑,这样的代码是没有问题的,当函数返回时,pInst总是指向一个有效对象。而lock和unlock防止多线程竞争导致的麻烦。
但是实际上这样的代码是有问题的。问题的来源任然是CPU的乱序执行。C++里的new其实包含了两个步骤:
(1)分配内存。
(2)调用构造函数。
所以pInst = new T()包含了三个步骤:
(1)分配内存。
(2)在内存位置上调用构造函数。
(3)将内存地址赋值给pInst。
在这三步中,(2)和(3)的顺序是可以颠倒的。也就是说,完全有可能出现这样的情况:pInst的值已经不是NULL,但对象任然没有构造完毕。这时候如果出现另外一个对GetInstance的并发调用,此时第一个if内的表达式pInst==NULL为false,所以这个调用会直接返回尚未构造完全的对象的地址(pInst)以提供给用户使用。
为此,阻止CPU换序是必需的。遗憾的是,现在并不存在可移植的阻止换序的方法。通常情况下是调用CPU提供的一条指令,这条指令常常被称为barrier。一条barrier指令会阻止CPU将该指令之前的指令交换到barrier之后,反之亦然。换句话说,barrier指令的作用类似于一个拦水坝,阻止换序“穿透”这个大坝。
许多体系结构的CPU都提供barrier指令,不过它们的名称各不相同,例如POWERPC提供的其中一条指令名叫lwsync。我们可以这样来保证线程安全:
#define barrier() __asm__ volaticle("lwsync") volatile T* pInst = 0; T* GetInstance() { if (pInst == NULL) { lock(); if (pInst == NULL) { T* tmp = new T(); barrier(); pInst = tmp; } unlock(); } return pInst; }
由于barrier的存在,对象的构造一定在barrier执行之前完成,因此当pInst被赋值时,对象总是完好的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了