Memory Ordering
生活随笔
收集整理的這篇文章主要介紹了
Memory Ordering
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
Background
很久很久很久以前,CPU忠厚老實(shí),一條一條指令的執(zhí)行我們給它的程序,規(guī)規(guī)矩矩的進(jìn)行計(jì)算和內(nèi)存的存取。
很久很久以前, CPU學(xué)會(huì)了Out-Of-Order,CPU有了Cache,但一切都工作的很好,就像很久很久很久以前一樣,而且工作效率得到了很大的提高。
很久以前,我們需要多個(gè)CPU一起工作,于是出現(xiàn)了傳說中的SMP系統(tǒng),每個(gè)CPU都有獨(dú)立的Cache,都會(huì)亂序執(zhí)行,會(huì)打亂內(nèi)存存取順序,于是事情變得復(fù)雜了…… Problem
由于每個(gè)CPU都有自己的Cache,內(nèi)存讀寫不再一定需要真的作內(nèi)存訪問,而是直接從Cache里面操作,同時(shí)CPU可能會(huì)在合適的時(shí)候?qū)τ趦?nèi)存訪問進(jìn)行重新排序以提高效率,在只有一個(gè)CPU的時(shí)候,這很完美。
而當(dāng)有多個(gè)CPU的時(shí)候——
Thread A:
while (flag == 0)
??????? ; // do nothing
printf("%d\n", data);
Thread B:
data = 523;
flag = 1;
這里data代表了某種數(shù)據(jù),它可以像這里一樣是一個(gè)簡單的整數(shù),也可能是某種復(fù)雜的數(shù)據(jù)結(jié)構(gòu),總之,我們?cè)赥hread B中對(duì)data進(jìn)行了寫入,并利用flag變量表示data已經(jīng)準(zhǔn)備好了。
在Thread A中,一個(gè)忙等待直到發(fā)現(xiàn)data已經(jīng)準(zhǔn)備好了,然后開始使用data,這里是簡單的把data打印出來。
現(xiàn)在考慮如果CPU發(fā)現(xiàn)對(duì)于data和flag的寫入,如果按照先寫入flag后寫入data的方式進(jìn)行,或者考慮由于Cache的flush操作的延遲,使得內(nèi)存中變量的實(shí)際修改順序是先flag后data,那么都將導(dǎo)致Thread A的結(jié)果不正確。事實(shí)上,由于內(nèi)存讀入操作同樣是可能亂序進(jìn)行的,Thread A甚至可能在讀入flag進(jìn)行判斷之前就已經(jīng)完成了對(duì)data的讀入操作,這同樣導(dǎo)致錯(cuò)誤的結(jié)果。 Solution
在這個(gè)例子中,我們的需求是,Thread A中對(duì)于flag判斷時(shí),后面的任何讀入操作都沒有開始,Thread B中對(duì)于flag寫入時(shí),任何之前的寫入操作都已經(jīng)完成。
在Linux內(nèi)核中,smp_rmb()、smp_wmb()、smp_mb()就是用來解決這類問題的,mb表示memory barrier。rmb表示讀操作不可跨越(注意,不是人民幣的意思:-P),也就是我們這個(gè)例子中的Thread A所需要的。wmb表示寫操作不可跨越,也就是這里Thread B所需要的。mb集合了rmb和wmb的能力,讀寫操作都不可跨越。
在Qt中,其支持原子操作的類QAtomicInt支持四種類型的操作,Relaxed、Acquired、Release、Ordered,其中 Relaxed最為簡單,就是不做特殊要求,由編譯器和處理器對(duì)讀寫進(jìn)行合適的排序。Acquired表示原子操作之后的內(nèi)存操作不可被重排至原子操作之前。Release表示原子操作之前的內(nèi)存操作不可被重排至原子操作之后。Ordered表示Acquired + Release。在前面的例子中,Thread A對(duì)于flag的讀取操作需要Acquired版本,而Thread B對(duì)于flag的寫入操作需要Release版本。
在實(shí)際實(shí)現(xiàn)中,不同體系結(jié)構(gòu)的實(shí)現(xiàn)方法各不相同,很多RISC機(jī)器提供了專門的指令用于實(shí)現(xiàn)mb,而在x86上面,通常使用lock指令前綴加上一個(gè)空操作來實(shí)現(xiàn),注意當(dāng)然不能真的是nop指令,但是可以用來實(shí)現(xiàn)空操作的指令其實(shí)是很多的,比如Linux中采用的addl $0, 0(%esp)。Qt的不同類型原子操作由于本身就需要進(jìn)行某種可被lock前綴修飾的操作,所以就不需要畫蛇添足的再寫一條空操作了,比如 testAndSetOrdered就可以直接使用lock cmpxchgl實(shí)現(xiàn)。
很久很久很久以前,CPU忠厚老實(shí),一條一條指令的執(zhí)行我們給它的程序,規(guī)規(guī)矩矩的進(jìn)行計(jì)算和內(nèi)存的存取。
很久很久以前, CPU學(xué)會(huì)了Out-Of-Order,CPU有了Cache,但一切都工作的很好,就像很久很久很久以前一樣,而且工作效率得到了很大的提高。
很久以前,我們需要多個(gè)CPU一起工作,于是出現(xiàn)了傳說中的SMP系統(tǒng),每個(gè)CPU都有獨(dú)立的Cache,都會(huì)亂序執(zhí)行,會(huì)打亂內(nèi)存存取順序,于是事情變得復(fù)雜了…… Problem
由于每個(gè)CPU都有自己的Cache,內(nèi)存讀寫不再一定需要真的作內(nèi)存訪問,而是直接從Cache里面操作,同時(shí)CPU可能會(huì)在合適的時(shí)候?qū)τ趦?nèi)存訪問進(jìn)行重新排序以提高效率,在只有一個(gè)CPU的時(shí)候,這很完美。
而當(dāng)有多個(gè)CPU的時(shí)候——
- 從Cache到內(nèi)存的flush操作通常是被延遲的,所以就需要某種方法保證CPU A進(jìn)行的內(nèi)存寫操作真的可以被CPU B讀取到。
- CPU可能會(huì)因?yàn)槟承┰?#xff08;比如某兩個(gè)變量同在一個(gè)Cacheline中)而打亂
- 實(shí)際內(nèi)存寫入順序
- 實(shí)際內(nèi)存讀取順序
- 之前的讀寫操作已經(jīng)完成
- 未來的讀寫操作還沒開始
Thread A:
while (flag == 0)
??????? ; // do nothing
printf("%d\n", data);
Thread B:
data = 523;
flag = 1;
這里data代表了某種數(shù)據(jù),它可以像這里一樣是一個(gè)簡單的整數(shù),也可能是某種復(fù)雜的數(shù)據(jù)結(jié)構(gòu),總之,我們?cè)赥hread B中對(duì)data進(jìn)行了寫入,并利用flag變量表示data已經(jīng)準(zhǔn)備好了。
在Thread A中,一個(gè)忙等待直到發(fā)現(xiàn)data已經(jīng)準(zhǔn)備好了,然后開始使用data,這里是簡單的把data打印出來。
現(xiàn)在考慮如果CPU發(fā)現(xiàn)對(duì)于data和flag的寫入,如果按照先寫入flag后寫入data的方式進(jìn)行,或者考慮由于Cache的flush操作的延遲,使得內(nèi)存中變量的實(shí)際修改順序是先flag后data,那么都將導(dǎo)致Thread A的結(jié)果不正確。事實(shí)上,由于內(nèi)存讀入操作同樣是可能亂序進(jìn)行的,Thread A甚至可能在讀入flag進(jìn)行判斷之前就已經(jīng)完成了對(duì)data的讀入操作,這同樣導(dǎo)致錯(cuò)誤的結(jié)果。 Solution
在這個(gè)例子中,我們的需求是,Thread A中對(duì)于flag判斷時(shí),后面的任何讀入操作都沒有開始,Thread B中對(duì)于flag寫入時(shí),任何之前的寫入操作都已經(jīng)完成。
在Linux內(nèi)核中,smp_rmb()、smp_wmb()、smp_mb()就是用來解決這類問題的,mb表示memory barrier。rmb表示讀操作不可跨越(注意,不是人民幣的意思:-P),也就是我們這個(gè)例子中的Thread A所需要的。wmb表示寫操作不可跨越,也就是這里Thread B所需要的。mb集合了rmb和wmb的能力,讀寫操作都不可跨越。
在Qt中,其支持原子操作的類QAtomicInt支持四種類型的操作,Relaxed、Acquired、Release、Ordered,其中 Relaxed最為簡單,就是不做特殊要求,由編譯器和處理器對(duì)讀寫進(jìn)行合適的排序。Acquired表示原子操作之后的內(nèi)存操作不可被重排至原子操作之前。Release表示原子操作之前的內(nèi)存操作不可被重排至原子操作之后。Ordered表示Acquired + Release。在前面的例子中,Thread A對(duì)于flag的讀取操作需要Acquired版本,而Thread B對(duì)于flag的寫入操作需要Release版本。
在實(shí)際實(shí)現(xiàn)中,不同體系結(jié)構(gòu)的實(shí)現(xiàn)方法各不相同,很多RISC機(jī)器提供了專門的指令用于實(shí)現(xiàn)mb,而在x86上面,通常使用lock指令前綴加上一個(gè)空操作來實(shí)現(xiàn),注意當(dāng)然不能真的是nop指令,但是可以用來實(shí)現(xiàn)空操作的指令其實(shí)是很多的,比如Linux中采用的addl $0, 0(%esp)。Qt的不同類型原子操作由于本身就需要進(jìn)行某種可被lock前綴修飾的操作,所以就不需要畫蛇添足的再寫一條空操作了,比如 testAndSetOrdered就可以直接使用lock cmpxchgl實(shí)現(xiàn)。
?
轉(zhuǎn)貼自:http://etrnlog.appspot.com/2009/10/12/memory-ordering.html
轉(zhuǎn)載于:https://www.cnblogs.com/codingmylife/archive/2010/04/28/1722573.html
總結(jié)
以上是生活随笔為你收集整理的Memory Ordering的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面向对象编程(Object Orient
- 下一篇: rails采用MongoDB感觉相当不错