请看下面的代码并尝试猜测输出:
可能一看下面的代码你可能会放弃继续看了,但如果你想要彻底弄明白volatile,你需要耐心,下面的代码很简单!
在下面的代码中,我们定义了4个字段x,y,a和b,它们被初始化为0
然后,我们创建2个分别调用Test1和Test2的任务,并等待两个任务完成。
完成两个任务后,我们检查a和b是否仍为0,
如果是,则打印它们的值。
最后,我们将所有内容重置为0,然后一次又一次地运行相同的循环。
如果您运行上述代码(最好在Release模式下运行),则会看到输出为0、0的许多输出,如下图。
我们先根据代码自我分析下在Test1中,我们将x设置为1,将a设置为y,而Test2将y设置为1,将b设置为x
因此这4条语句会在2个线程中竞争
罗列下可能会发生的几种情况:
在这种情况下,我们假设Test1在Test2之前完成,那么最终值将是
x = 1,a = 0,y = 1,b = 1 2. Test2执行完成后执行Test1: y = 1 b = x x = 1 a = y在这种情况下,那么最终值将是
x = 1,a = 1,y = 1,b = 0 2. Test1执行期间执行Test2: x = 1 y = 1 b = x a = y在这种情况下,那么最终值将是
x = 1,a = 1,y = 1,b = 1 3. Test2执行期间执行Test1 y = 1 x = 1 a = y b = x在这种情况下,那么最终值将是
x = 1,a = 1,y = 1,b = 1 4. Test1交织Test2 x = 1 y = 1 a = y b = x在这种情况下,那么最终值将是
x = 1,a = 1,y = 1,b = 1 5.Test2交织Test1 y = 1 x = 1 b = x a = y在这种情况下,那么最终值将是
x = 1,a = 1,y = 1,b = 1我认为上面已经罗列的
已经涵盖了所有可能的情况,
但是无论发生哪种竞争情况,
看起来一旦两个任务都完成,
就不可能使a和b都同时为零,
但是奇迹般地,居然一直在打印0,0 (请看上面的动图,如果你怀疑的话代码copy执行试试)
先揭晓答案:cpu的乱序执行。
让我们看一下Test1和Test2的IL中间代码。
我在相关部分中添加了注释。
请注意,我在注释中使用“上载”和“下载”一词,而不是传统的读/写术语。
为了从变量中读取值并将其分配到另一个存储位置,
我们必须将其读取到CPU寄存器(如上面的edx),
然后才能将其分配给目标变量。
由于CPU操作非常快,因此与在CPU中执行的操作相比,对内存的读取或写入真的很慢。
所以我使用“上传”和“下载”,相对于CPU的高速缓存而言【读取和写入内存的行为】
就像我们向远程Web服务上载或从中下载一样慢。
L1 cache reference: 1 ns
L2 cache reference: 4 ns
Branch mispredict: 3 ns
Mutex lock/unlock: 17 ns
Main memory reference: 100 ns
Compress 1K bytes with Zippy: 2000 ns
Send 2K bytes over commodity network: 44 ns
Read 1 MB sequentially from memory: 3000 ns
Round trip within same datacenter: 500,000 ns
Disk seek: 2,000,000 ns
Read 1 MB sequentially from disk: 825,000 ns
Read 1 MB sequentially from SSD: 49000 ns
如果让你开发一个应用程序,实现上载或者下载功能。
您将如何设计此?肯定想要开多线程,并行化执行以节省时间!
这正是CPU的功能。CPU被我们设计的很聪明,
在实际运行中可以确定某些“上载”和“下载”操作(指令)不会互相影响,
并且CPU为了节省时间,对它们(指令)进行了(优化)并行处理,
也叫【cpu乱序执行】(out-of-order)