關于DBSvr啟動時加載所有數據到內存
仔細想想有兩個問題,
數據加載到內存,以什么樣的形式存貯,像DB里的表一樣,還是按游戲邏輯來存,
如果像表一樣,那實現一個像DBMS一樣高效的查表功能應該是個問題。
如果按游戲邏輯來存, 那DBSvr又會跟游戲邏輯層扯上太多聯系。
看來,讀所有數據到內存是不現實的。不知道各個有什么看法,請多多指教
posted @ 2012-07-13 12:13 多彩人生 閱讀(242) | 評論 (0) | 編輯 收藏
zc qq:1337220912
posted @ 2012-07-13 12:13 多彩人生 閱讀(242) | 評論 (0) | 編輯 收藏
1、 游戲世界由很多個游戲對象組成(游戲角色、物品、NPC、技能等);
2、 一個游戲對象的有效數據主要存放在客戶端、游戲服務器和持久性數據庫中;
3、 游戲對象的處理可劃分為與位置有關的和與位置無關的,如公會處理、物品處理等主要行為可以看作是與位置無關的處理,而NPC(AI)、戰斗、移動這類的主要行為可以看成是與位置有關的。
4、 從客戶端的角度來看,游戲行為可分為四類動作:
a) 來自服務器端的動作,如另外一個玩家跳起來。
b) 本地動作。僅僅發生在本地客戶端的動作,不需要與服務器端或其他客戶端通訊。
c) 先執行后驗證的可撤銷的動作。客戶端先執行,再提交服務器端驗證,驗證不成功通知客戶端將執行的動作撤銷。比如玩家控制的游戲角色執行移動處理。
d) 嚴格服務器端驗證的動作。客戶端執行動作前必須經過服務器端驗證后才能執行。如交易行為、攻擊其他玩家/NPC。
5、 客戶端和服務器,服務器進程之間的相互的通信從邏輯上看就是就是向RemoteObject 發起的遠程過程調用(RPC),RPC主要有兩種類型:
a) 通知(Notify)。只通知對方,而不關心和需要對方返回結果。
b) 請求(Request)。向對方發起請求,對方處理請求后返回結果,發起請求和返回結果這個過程可以是同步或異步。游戲服務器中絕大部分RPC請求都是異步的。
6、 響應延遲主要是由于網絡帶寬和服務器處理效率引起的。應盡可能的通過一些技巧來隱藏和減少玩家的響應延遲。但不是所有的最新消息都能立刻發送出去(或接收處理到),因此,要在服務器端采用優先隊列來減少重要消息的響應時間。延遲也會由客戶端產生,如收到消息后的對消息的處理速度。
7、 服務器負載,除了升級硬件設備外,可以通過一些方式來提高服務器負載。
a) 保證足夠的網絡帶寬。
b) 分布式運算,合理的集群式架構。
c) 游戲策劃從游戲內容上避免設計高并發,高消耗的游戲行為。
8、 從服務器的可伸縮性,穩定性和高效率方面來考慮,要試著避免所有事情都在一個地方處理,盡量讓系統分布式運行,但是過多的劃分功能到不同的進程/機器上運行,又會帶來數據的大量同步的問題。因此可以將游戲對象的處理主要劃分為與位置無關和有關兩種。像公會,玩家信息,物品信息,組隊,拍賣等等這類與位置無關的但是占用CPU資源較少的處理可以盡可能的放在一個進程中,避免進程間對象同步,而像NPC,尋路,AOI運算,戰斗處理等與位置有關的,處理過程中特別關心對象坐標位置的、運算量特別大的,但是進程間對象同步較少的,都可以單獨劃分成多個進程。
每類進程服務的功能盡量單一。負責路由的就盡量只負責網絡包轉發,而不再承擔其他繁重的任務,負責游戲處理的就盡量讓網絡包流向簡單。
posted @ 2012-07-12 16:06 多彩人生 閱讀(166) | 評論 (0) | 編輯 收藏
STL的map表里有一個erase方法用來從一個map中刪除掉指令的節點
eg:
map<string,string> mapTest;
typedef map<string,string>::iterator ITER;
ITER iter=mapTest.find(key);
mapTest.erase(iter);
像上面這樣只是刪除單個節點,map的形為不會出現任務問題,
但是當在一個循環里用的時候,往往會被誤用,那是因為使用者沒有正確理解iterator的概念.
像下面這樣的一個例子就是錯誤的寫法,
eg.
for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter);
}
這是一種錯誤的寫法,會導致程序行為不可知.究其原因是map 是關聯容器,對于關聯容器來說,如果某一個元素已經被刪除,那么其對應的迭代器就失效了,不應該再被使用;否則會導致程序無定義的行為。
可以用以下方法解決這問題:
正確的寫法
1.使用刪除之前的迭代器定位下一個元素。STL建議的使用方式
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
mapTest.erase(iter++);
}
2. erase() 成員函數返回下一個元素的迭代器
for(ITER iter=mapTest.begin();iter!=mapTest.end();)
{
cout<<iter->first<<":"<<iter->second<<endl;
iter=mapTest.erase(iter);
}
注意:
map的earse應注意:
map這種容器的下邊訪問和Vector等容器的下標訪問有本質的區別。
對于Vector容器,用aVector[i]訪問第i個元素時,如果元素不存在,容器不會增加元素,
而對于map,用aMap[key]
訪問鍵key對應的對象時,如果該鍵對應的對象存在,則返回該對象(這和Vector一樣),但是,當鍵值為key的元素不存在時,容器會自動的增加一個 pair,鍵為key,而值則為一個容器定義時指定的類型并默認初始化(即,如果該類型為基本類型,則初始化為0,比如本例中,aMap[1]的使用會產 生一個pair,<1,NULL>,若該類型為類類型,則調用默認構造函數初始化之)
顯然,本例中,aMap[1]為NULL,后面的erase()不會執行,使得后面的
插入語句aMap.insert(1,new A())鍵值沖突
eg:如下代碼會導致錯誤
#include <iostream>
#include <map>
using namespace std;
struct A
{
A(int i)
{
x=i;
}
int x;
};
int main()
{
map<int,A*> amap;
if ( amap[1] != NULL )
amap.erase(1);
amap.insert(make_pair(1,new A(1)));
amap.insert(make_pair(2,new A(2)));
amap.insert(make_pair(3,new A(3)));
return 0;
}
posted @ 2012-07-06 10:20 多彩人生 閱讀(250) | 評論 (0) | 編輯 收藏
1、是Header Plus Plus 的簡寫。
2、與*.h類似,hpp是C++程序頭文件 。
3、是VCL 專用的頭文件,已預編譯。
4、是一般模板類的頭文件。
5、一般來說,*.h里面只有聲明,沒有實現,而*.hpp里聲明實現都有,后者可以減 少.cpp的數量。
6、*.h里面可以有using namespace std,而*.hpp里則無。
7、*.hpp要注意的問題有:
a)不可包含全局對象和全局函數
由于hpp本質上是作為.h被調用者include,所以當hpp文件中存在全局對象或者全局函數,而該hpp被多個
調用者include時,將在鏈接時導致符號重定義錯誤。要避免這種情況,需要去除全局對象,將全局函數封
裝為類的靜態方法。
b)類之間不可循環調用
在.h和.cpp的場景中,當兩個類或者多個類之間有循環調用關系時,只要預先在頭文件做被調用類的聲明
即可,如下:
class B;
class A{
public:
void someMethod(B b);
};
class B{
public:
void someMethod(A a);
};
在hpp場景中,由于定義與實現都已經存在于一個文件,調用者必需明確知道被調用者的所有定義,而不能等到cpp
中去編譯。因此hpp中必須整理類之間調用關系,不可產生循環調用。同理,對于當兩個類A和B分別定義在各自的
hpp文件中,形如以下的循環調用也將導致編譯錯誤:
//a.hpp
#include "b.hpp"
class A{
public:
void someMethod(B b);
};
//b.hpp
#include "a.hpp"
class B{
public:
void someMethod(A a);
}
c)不可使用靜態成員
靜態成員的使用限制在于如果類含有靜態成員,則在hpp中必需加入靜態成員初始化代碼,當該hpp被多個文檔include時,將產生符號重定義錯誤。唯 一的例外是const static整型成員,因為在vs2003中,該類型允許在定義時初始化,如:
class A{
public:
const static int intValue = 123;
};
由于靜態成員的使用是很常見的場景,無法強制清除,因此可以考慮以下幾種方式(以下示例均為同一類中方法)
一、類中僅有一個靜態成員時,且僅有一個調用者時,可以通過局域靜態變量模擬
//方法模擬獲取靜態成員
someType getMember()
{
static someType value(xxx);//作用域內靜態變量
return value;
}
二、.類中有多個方法需要調用靜態成員,而且可能存在多個靜態成員時,可以將每個靜態成員封裝一個模擬方法,供其他方法調用。
someType getMemberA()
{
static someType value(xxx);//作用域內靜態變量
return value;
}
someType getMemberB()
{
static someType value(xxx);//作用域內靜態變量
return value;
}
void accessMemberA()
{
someType member = getMemberA();//獲取靜態成員
};
//獲取兩個靜態成員
void accessStaticMember()
{
someType a = getMemberA();//獲取靜態成員
someType b = getMemberB();
};
三、第二種方法對于大部分情況是通用的,但是當所需的靜態成員過多時,編寫封裝方法的工作量將非常
巨大,在此種情況下,建議使用Singleton模式,將被調用類定義成普通類,然后使用Singleton將其變為
全局唯一的對象進行調用。
如原h+cpp下的定義如下:
class A{
public:
type getMember(){
return member;
}
static type member;//靜態成員
}
采用singleton方式,實現代碼可能如下(singleton實現請自行查閱相關文檔)
//實際實現類
class Aprovider{
public:
type getMember(){
return member;
}
type member;//變為普通成員
}
//提供給調用者的接口類
class A{
public:
type getMember(){
return Singleton<AProvider>::getInstance()->getMember();
}
}
posted @ 2012-07-03 17:47 多彩人生 閱讀(332) | 評論 (0) | 編輯 收藏
這篇文章詳細剖析了為什么在多核時代進行多線程編程時需要慎用volatile關鍵字。
主要內容有:
1. C/C++中的volatile關鍵字
2. Visual Studio對C/C++中volatile關鍵字的擴展
3. Java/.NET中的volatile關鍵字
4. Memory Model(內存模型)
5. Volatile使用建議
C/C++作為系統級語言,它們與硬件的聯系是很緊密的。volatile的意思是“易變的”,這個關鍵字最早就是為了針對那些“異常”的內存操作 而準備的。它的效果是讓編譯器不要對這個變量的讀寫操作做任何優化,每次讀的時候都直接去該變量的內存地址中去讀,每次寫的時候都直接寫到該變量的內存地 址中去,即不做任何緩存優化。它經常用在需要處理中斷的嵌入式系統中,其典型的應用有下面幾種:
a. 避免用通用寄存器對內存讀寫的優化。編譯器常做的一種優化就是:把常用變量的頻繁讀寫弄到通用寄存器中,最后不用的時候再存回內存中。但是如果某個內存地址中的值是由片外決定的(例如另一個線程或是另一個設備可能更改它),那就需要volatile關鍵字了。(感謝Kenny老師指正)
b. 硬件寄存器可能被其他設備改變的情況。例如一個嵌入式板子上的某個寄存器直接與一個測試儀器連在一起,這樣在這個寄存器的值隨時可能被那個測試儀器更改。 在這種情況下如果把該值設為volatile屬性的,那么編譯器就會每次都直接從內存中去取這個值的最新值,而不是自作聰明的把這個值保留在緩存中而導致 讀不到最新的那個被其他設備寫入的新值。
c. 同一個物理內存地址M有兩個不同的內存地址的情況。例如兩個程序同時對同一個物理地址進行讀寫,那么編譯器就不能假設這個地址只會有一個程序訪問而做緩存優化,所以程序員在這種情況下也需要把它定義為volatile的。
看到這里,很多朋友自然會想到:恩,那么如果是兩個線程需要同時訪問一個共享變量,為了讓其中兩個線程每次都能讀到這個變量的最新值,我們就把它定 義為volatile的就好了嘛!我想這個就是多線程程序中volatile之所以引起那么多爭議的最大原因。可惜的是,這個想法是錯誤的。
舉例來說,想用volatile變量來做同步(例如一個flag)?錯!為什么?很簡單,雖然volatile意味著每次讀和寫都是直接去內存地址中去操作,但是volatile在C/C++現有標準中即不能保證原子性(Atomicity)也不能保證順序性(Ordering),所以幾乎所有試圖用volatile來進行多線程同步的方案都是錯的。我之前一篇文章介紹了Sequential Consistency模型(后 面簡稱SC),它其實就是我們印象中多線程程序應該有的執行順序。但是,SC最大的問題是性能太低了,因為CPU/編譯器完全沒有必要嚴格按代碼規定的順 序(program order)來執行每一條指令。學過體系結構的同學應該知道不管是編譯器也好CPU也好,他們最擅長做的事情就是幫你做亂序優化。在串行時代這些亂序優化 對程序員來說都是透明的,封裝好了的,你不用關心它們到底給你亂序成啥樣了,因為它們會保證優化后的程序的運行結果跟你寫程序時預期的結果是一模一樣的。 但是進入多核時代之后,CPU和編譯器還會繼續做那些串行時代的優化,更重要的是這些優化還會打破你多線程程序的SC模型語義,從而使得多線程程序的實際 運行結果與我們所期待的運行結果不一致!
拿X86來說,它的多核內存模型沒有嚴格執行SC,即屬于weak ordering(或者叫relax ordering?)。它唯一允許的亂序優化是可以把對不同地址的load操作提到store之前去(即把store x->load y亂序優化成load y -> store x)。而store x -> store y、load x -> load y,以及store x -> load y不允許交換執行順序。在X86這樣的內存模型下,volatile關鍵字根本就不能保證對不同volatile變量x和y的store x -> load y的操作不會被CPU亂序優化成load y -> store x。
而對多線程讀寫操作的原子性來說,諸如volatile x=1這樣的寫操作的原子性其實是由X86硬件保證的,跟volatile沒有任何關系。事實上,volatile根本不能保證對沒有內存對齊的變量(或者超出機器字長的變量)的讀寫操作的原子性。
為了有個更直觀的理解,我們來看看CPU的亂序優化是如何讓volatile在多線程程序中顯得如此無力的。下面這個著名的Dekker算法是想用 flag1/2和turn來實現兩個線程情況下的臨界區互斥訪問。這個算法關鍵就在于對flag1/2和turn的讀操作(load)是在其寫操作 (store)之后的,因此這個多線程算法能保證dekker1和dekker2中對gSharedCounter++的操作是互斥的,即等于是把 gSharedCounter++放到臨界區里去了。但是,多核X86可能會對這個store->load操作做亂序優化,例如dekker1中對 flag2的讀操作可能會被提到對flag1和turn的寫操作之前,這樣就會最終導致臨界區的互斥訪問失效,而gSharedCounter++也會因 此產生data race從而出現錯誤的計算結果。那么為什么多核CPU會對多線程程序做這樣的亂序優化呢?因為從單線程的視角來看flag2和flag1、turn是沒 有依賴關系的,所以CPU當然可以對他們進行亂序優化以便充分利用好CPU里面的流水線(想了解更多細節請參考計算機體系結構相關書籍)。這樣的優化雖然 從單線程角度來講沒有錯,但是它卻違反了我們設計這個多線程算法時所期望的那個多線程語義。(想要解決這個bug就需要自己手動添加memory barrier,或者干脆別去實現這樣的算法,而是使用類似pthread_mutex_lock這樣的庫函數,后面我會再講到這點)
當然,對不同的CPU來說他們的內存模型是不同的。比如說,如果這個程序是在單核上以多線程的方式執行那么它肯定不會出錯,因為單核CPU的內存模 型是符合SC的。而在例如PowerPC,ARM之類的架構上運行結果到底如何就得去翻它們的硬件手冊中內存模型是怎么定義的了。
雖然C/C++中的volatile關鍵字沒有對ordering做任何保證,但是微軟從Visual Studio 2005開始就對volatile關鍵字添加了同步語義(保證ordering),即:對volatile變量的讀操作具有acquire語義,對 volatile變量的寫操作具有release語義。Acquire和Release語義是來自data-race-free模型的概念。為了理解這個 acquire語義和release語義有什么作用,我們來看看MSDN中的一個例子。
例子中的 while (Sentinel) Sleep(0); // volatile spin lock 是對volatile變量的讀操作,它具有acquire語義,acquire語義的隱義是當前線程在對sentinel的這個讀操作之后的所有的對全局 變量的訪問都必須在該操作之后執行;同理,例子中的Sentinel = false; // exit critical section 是對volatile變量的寫操作,它具有release語義,release語義的隱義是當前線程在對sentinel這個寫操作之前的所有對全局變量 的訪問都必須在該操作之前執行完畢。所以ThreadFunc1()讀CriticalData時必定已經在ThreadFunc2()執行完 CriticalData++之后,即CriticalData最后輸出的值必定為1。建議大家用紙畫一下acquire/release來加深理解。一個比較形象的解釋就是把acquire當成lock,把release當成unlock,它倆組成了一個臨界區,所有臨界區外面的操作都只能往這個里面移,但是臨界區里面的操作都不能往外移,簡單吧?
其實這個程序就相當于用volatile變量的acquire和release語義實現了一個臨界區,在臨界區內部的代碼就是 Sleep(2000); CriticalData++; 或者更貼切點也可以看成是一對pthread_cond_wait和pthread_cond_signal。
這個volatile的acquire和release語義是VS自己的擴展,C/C++標準里是沒有的,所以同樣的代碼用gcc編譯執行結果就可 能是錯的,因為編譯器/CPU可能做違反正確性的亂序優化。Acquire和release語義本質上就是為了保證程序執行時memory order的正確性。但是,雖然這個VS擴展使得volatile變量能保證ordering,它還是不能保證對volatile變量讀寫的原子性。事 實上,如果我們的程序是跑在X86上面的話,內存對齊了的變量的讀寫的原子性是由硬件保證的,跟volatile沒有任何關系。而像volatile g_nCnt++這樣的語句本身就不是原子操作,想要保證這個操作是原子的,就必須使用帶LOCK語義的++操作,具體請看我這篇文章。
另外,VS生成的volatile變量的匯編代碼是否真的調用了memory barrier也得看具體的硬件平臺,例如x86上就不需要使用memory barrier也能保證acquire和release語義,因為X86硬件本身就有比較強的memory模型了,但是Itanium上面VS就會生成帶 memory barrier的匯編代碼。具體可以參考這篇。
但是,雖然VS對volatile關鍵字加入了acquire/release語義,有一種情況還是會出錯,即我們之前看到的dekker算法的例子。這 個其實蠻好理解的,因為讀操作的acquire語義不允許在其之后的操作往前移,但是允許在其之前的操作往后移;同理,寫操作的release語義允許在 其之后的操作往前移,但是不允許在其之前的操作往后移;這樣的話對一個volatile變量的讀操作(acquire)當然可以放到對另一個 volatile變量的寫操作(release)之前了!Bug就是這樣產生的!下面這個程序大家拿Visual Studio跑一下就會發現bug了(我試了VS2008和VS2010,都有這個bug)。多線程編程復雜吧?希望大家還沒被弄暈,要是暈了的話也很正 常,仔仔細細重新再看一遍吧:)
想解決這個Bug也很簡單,直接在dekker1和dekker2中對flag1/flag2/turn賦值操作之后都分別加入full memory barrier就可以了,即保證load一定是在store之后執行即可。具體的我就不詳述了。
Java和.NET分別有JVM和CLR這樣的虛擬機,保證多線程的語義就容易多了。說簡單點,Java和.NET中的volatile關鍵字也是限制虛擬機做優化,都具有acquire和release語義,而且由虛擬機直接保證了對volatile變量讀寫操作的原子性。 (volatile 只保證可見性,不保證原子性。java中,對volatile修飾的long和double的讀寫就不是原子的 (http://java.sun.com/docs/books/jvms/second_edition/html /Threads.doc.html#22244),除此之外的基本類型和引用類型都是原子的。– 多謝liuchangit指正) 這 里需要注意的一點是,Java和.NET里面的volatile沒有對應于我們最開始提到的C/C++中對“異常操作”用volatile修飾的傳統用 法。原因很簡單,Java和.NET的虛擬機對安全性的要求比C/C++高多了,它們才不允許不安全的“異常”訪問存在呢。
而且像JVM/.NET這樣的程序可移植性都非常好。雖然現在C++1x正在把多線程模型添加到標準中去,但是因為C++本身的性質導致它的硬件平 臺依賴性很高,可移植性不是特別好,所以在移植C/C++多線程程序時理解硬件平臺的內存模型是非常重要的一件事情,它直接決定你這個程序是否會正確執 行。
至于Java和.NET中是否也存在類似VS 2005那樣的bug我沒時間去測試,道理其實是相同的,真有需要的同學自己應該能測出來。好像這篇InfoQ的文章中顯示Java運行這個dekker算法沒有問題,因為JVM給它添加了mfence。另一個臭名昭著的例子就應該是Double-Checked Locking了。
Java和.NET中這兩者還是有些區別的,主要就是后者提供了類似incrementAndGet()這樣的方法可以直接調用(保證了原子性), 而如果是volatile x進行++操作則不是原子的。increaseAndGet()的實現調用了類似CAS這樣的原子指令,所以能保證原子性,同時又不會像使用 synchronized關鍵字一樣損失很多性能,用來做全局計數器非常合適。
說了這么多,還是順帶介紹一下Memory Model吧。就像前面說的,CPU硬件有它自己的內存模型,不同的編程語言也有它自己的內存模型。如果用一句話來介紹什么是內存模型,我會說它就是程序 員,編程語言和硬件之間的一個契約,它保證了共享的內存地址里的值在需要的時候是可見的。下次我會專門詳細寫一篇關于它的內容。它最大的作用是取得可編程 性與性能優化之間的一個平衡。
總的來說,volatile關鍵字有兩種用途:一個是ISO C/C++中用來處理“異常”內存行為(此用途只保證不讓編譯器做任何優化,對多核CPU是否會進行亂序優化沒有任何約束力),另一種是在Java /.NET(包括Visual Studio添加的擴展)中用來實現高性能并行算法(此種用途通過使用memory barrier保證了CPU/編譯器的ordering,以及通過JVM或者CLR保證了對該volatile變量讀寫操作的原子性)。
一句話,volatile對多線程編程是非常危險的,使用的時候千萬要小心你的代碼在多核上到底是不是按你所想的方式執行的,特別是對現在暫時還沒 有引入內存模型的C/C++程序更是如此。安全起見,大家還是用Pthreads,Java.util.concurrent,TBB等并行庫提供的 lock/spinlock,conditional variable, barrier, Atomic Variable之類的同步方法來干活的好,因為它們的內部實現都調用了相應的memory barrier來保證memory ordering,你只要保證你的多線程程序沒有data race,那么它們就能幫你保證你的程序是正確的(是的,Pthreads庫也是有它自己的內存模型的,只不過它的內存模型還些缺點,所以把多線程內存模 型直接集成到C/C++中是更好的辦法,也是將來的趨勢,但是C++1x中將不會像Java/.NET一樣給volatile關鍵字添加acquire和 release語義,而是轉而提供另一種具有同步語義的atomic variables,此為后話)。如果你想實現更高性能的lock free算法,或是使用volatile來進行同步,那么你就需要先把CPU和編程語言的memory model搞清楚,然后再時刻注意Atomicity和Ordering是否被保證了。(注 意,用沒有acquire/release語義的volatile變量來進行同步是錯誤的,但是你仍然可以在C/C++中用volatile來修飾一個不 是用來做同步(例如一個event flag)而只是被不同線程讀寫的共享變量,只不過它的新值什么時候能被另一個線程讀到是沒有保證的,需要你自己做相應的處理)
Herb Sutter 在他的那篇volatile vs. volatile中對這兩種用法做了很仔細的區分,我把其中兩張表格鏈接貼過來供大家參考:
最后附上《Java Concurrency in Practice》3.1.4節中對Java語言的volatile關鍵字的使用建議(不要被英語嚇到,這些內容確實對你有用,而且還能順便幫練練英語,哈哈):
So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block. However, we do not recommend relying too heavily on volatile variables for visibility; code that relies on volatile variables for visibility of arbitrary state is more fragile and harder to understand than code that uses locking.
Use volatile variables only when they simplify implementing and verifying your synchronization policy; avoid using volatile variables when veryfing correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifecycle event (such as initialization or shutdown) has occurred.
Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.
You can use volatile variables only when all the following criteria are met:
(1) Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value;
(2) The variable does not participate in invariants with other state variables; and
(3) Locking is not required for any other reason while the variable is being accessed.
1. 《Java Concurrency in Practice》3.1.4節
2. volatile vs. volatile(Herb Sutter對volatile的闡述,必看)
3. The “Double-Checked Locking is Broken” Declaration
4. Threading in C#
5. Volatile: Almost Useless for Multi-Threaded Programming
6. Memory Ordering in Modern Microprocessors
7. Memory Ordering @ Wikipedia
8. 內存屏障什么的
9. The memory model of x86
10. VC 下 volatile 變量能否建立 Memory Barrier 或并發鎖
11. Sayonara volatile(Concurrent Programming on Windows作者的文章 跟我觀點幾乎一致)
12. Java 理論與實踐: 正確使用 Volatile 變量
13. Java中的Volatile關鍵字
posted @ 2012-07-02 17:03 多彩人生 閱讀(463) | 評論 (0) | 編輯 收藏
posted @ 2012-06-30 12:01 多彩人生 閱讀(260) | 評論 (0) | 編輯 收藏
那么到底如何查找文件呢?我們需要一個結構體和幾個大家可能不太熟悉的函數。這些函數和結構體在<io.h>的頭文件中,結構體為 struct _finddata_t ,函數為_findfirst、_findnext和_fineclose.具體如何使用,我會慢慢講來~
首先講這個結構體吧~struct _finddata_t ,這個結構體是用來存儲文件各種信息的。說實話,這個結構體的具體定義代碼,我沒有找到,不過還好,文檔里面在_find里有比較詳細的成員變量介紹。我基本上就把文檔翻譯過來講吧:
unsigned atrrib:文件屬性的存儲位 置。它存儲一個unsigned單元,用于表示文件的屬性。文件屬性是用位表示的,主要有以下一些:_A_ARCH(存檔)、_A_HIDDEN(隱 藏)、_A_NORMAL(正常)、_A_RDONLY(只讀)、_A_SUBDIR(文件夾)、_A_SYSTEM(系統)。這些都是 在<io.h>中定義的宏,可以直接使用,而本身的意義其實是一個無符號整型(只不過這個整型應該是2的幾次冪,從而保證只有一位為1,而其 他位為0)。既然是位表示,那么當一個文件有多個屬性時,它往往是通過位或的方式,來得到幾個屬性的綜合。例如只讀+隱藏+系統屬性,應該 為:_A_HIDDEN | _A_RDONLY |_A_SYSTEM .
time_t time_create:這里的time_t是一個變量類型(長整型?相當于long int?),用來存儲時間的,我們暫時不用理它,只要知道,這個time_create變量是用來存儲文件創建時間的就可以了。
time_t time_access:文件最后一次被訪問的時間。
time_t time_write:文件最后一次被修改的時間。
_fsize_t size:文件的大小。這里的_fsize_t應該可以相當于unsigned整型,表示文件的字節數。
char name[_MAX_FNAME]:文件的文件名。這里的_MAX_FNAME是一個常量宏,它在<stdlib.h>頭文件中被定義,表示的是文件名的最大長度。
以此,我們可以推測出,struct_finddata_t ,大概的定義如下:
struct _finddata_t
{
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[_MAX_FNAME];
};
前面也說了,這個結構體是用來存儲文件信息的,那么如何把一個硬盤文件的文件信息“存到”這個結構體所表示的內存空間里去呢?這就要靠_findfirst、_findnext和_fineclose三個函數的搭配使用了。
首先還是對這三個函數一一介紹一番吧……
long _findfirst( char *filespec, struct _finddata_t *fileinfo );
返回值:如果查找成功的話,將返回一個long型的唯一的查找用的句柄(就是一個唯一編號)。這個句柄將在_findnext函數中被使用。若失敗,則返回-1.
參數:
filespec:標明文件的字符串,可支持通配符。比如:*.c,則表示當前文件夾下的所有后綴為C的文件。
fileinfo :這里就是用來存放文件信息的結構體的指針。這個結構體必須在調用此函數前聲明,不過不用初始化,只要分配了內存空間就可以了。函數成功后,函數會把找到的文件的信息放入這個結構體中。
int _findnext( long handle, struct _finddata_t *fileinfo );
返回值:若成功返回0,否則返回-1.
參數:
handle:即由_findfirst函數返回回來的句柄。
fileinfo:文件信息結構體的指針。找到文件后,函數將該文件信息放入此結構體中。
int _findclose( long handle );
返回值:成功返回0,失敗返回-1.
參數:
handle :_findfirst函數返回回來的句柄。
大家看到這里,估計都能猜到個大概了吧?先用_findfirst查找第一個文件,若成功則用返回的句柄調用_findnext函數查找其他的 文件,當查找完畢后用,用_findclose函數結束查找。恩,對,這就是正確思路。下面我們就按照這樣的思路來編寫一個查找C:\WINDOWS文件 夾下的所有exe可執行文件的程序。
#include <stdio.h>
#include <io.h>
const char *to_search="C:\\WINDOWS\\*.exe"; //欲查找的文件,支持通配符
int main()
{
long handle; //用于查找的句柄
struct _finddata_t fileinfo; //文件信息的結構體
handle=_findfirst(to_search,&fileinfo); //第一次查找
if(-1==handle)return -1;
printf("%s\n",fileinfo.name); //打印出找到的文件的文件名
while(!_findnext(handle,&fileinfo)) //循環查找其他符合的文件,知道找不到其他的為止
{
printf("%s\n",fileinfo.name);
}
_findclose(handle); //別忘了關閉句柄
system("pause");
return 0;
}
當然,這個文件的查找是在指定的路徑中進行,如何遍歷硬盤,在整個硬盤中查找文件呢?大家可以在網絡上搜索文件遞歸遍歷等方法,這里不再做進一步介紹。
細心的朋友可能會注意到我在程序的末尾用了一個system函數。這個與程序本身并沒有影響,和以前介紹給大家的使用getchar()函數的 作用相同,只是為了暫停一下,讓我們能看到命令提示符上輸出的結果而已。不過system函數本身是一個非常強大的函數。大家可以查查MSDN看看~簡單 來說,它是一個C語言與操作系統的相互平臺,可以在程序里通過這個函數,向操作系統傳遞command命令
posted @ 2012-06-28 21:37 多彩人生 閱讀(756) | 評論 (0) | 編輯 收藏
int main()
{
printf(STR(vck)); // 輸出字符串"vck"
printf("%d\n", CONS(2,3)); // 2e3 輸出:2000
return 0;
}
二、當宏參數是另一個宏的時候
需要注意的是凡宏定義里有用'#'或'##'的地方宏參數是不會再展開.
1, 非'#'和'##'的情況
#define TOW (2)
#define MUL(a,b) (a*b)
printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
這行的宏會被展開為:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的參數TOW會被展開為(2).
2, 當有'#'或'##'的時候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX #i nclude<climits>
這行會被展開為:
printf("int max: %s\n", "INT_MAX");
printf("%s\n", CONS(A, A)); // compile error
這一行則是:
printf("%s\n", int(AeA));
INT_MAX和A都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換宏.
加這層宏的用意是把所有宏的參數在這層里全部展開, 那么在轉換宏里的那一個宏(_STR)就能得到正確的宏參數.
#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 轉換宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 轉換宏
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX,int型的最大值,為一個變量 #i nclude<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再轉換成字符串;
printf("%d\n", CONS(A, A));
輸出為:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
三、'#'和'##'的一些應用特例
1、合并匿名變量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示該行行號;
第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二層: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三層: --> static int _anonymous70;
即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
2、填充結構
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當于:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE, "CLOSE"}};
3、記錄文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、得到一個數值類型所對應的字符串緩沖大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
這里相當于:
char buf[11];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
posted @ 2012-06-28 16:10 多彩人生 閱讀(379) | 評論 (0) | 編輯 收藏
自定義事件實現步驟有如下幾步:
1、定義自定義事件id
enum CustomEventId
{
ENUM_CUSTOMEVENT_ID_Id1=7000,
ENUM_CUSTOMEVENT_ID_Id2,
ENUM_CUSTOMEVENT_ID_Id3
};
2、申明自定義事件(.h文件中)
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1, ENUM_CUSTOMEVENT_ID_Id1)
DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2, ENUM_CUSTOMEVENT_ID_Id2)
DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3, ENUM_CUSTOMEVENT_ID_Id3)
END_DECLARE_EVENT_TYPES()
3、定義自定義事件(.cpp文件中)
DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1)
DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2)
DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3)
4、在BEGIN_EVENT_TABLE與END_EVENT_TABLE()添加事件映射
EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME1, Frame::OnSetName1)
EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME2, Frame::OnSetName2)
EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME3, Frame::OnSetName3)
5、在Frame中,申明、實現OnSetName1、OnSetName2、OnSetName3
申明:
void OnSetName1(wxCommandEvent& event);
void OnSetName2(wxCommandEvent& event);
void OnSetName3(wxCommandEvent& event);
實現:代碼就不在此列舉
6、自定義事件調用
wxCommandEvent eventCustom(ENUM_CUSTOMEVENT_NAME1);
wxPostEvent(this->GetParent()->GetEventHandler(), eventCustom); //子窗口
如果是當前窗口可以寫成
wxPostEvent(this->GetEventHandler(), eventCustom);
posted @ 2012-06-25 15:49 多彩人生 閱讀(849) | 評論 (0) | 編輯 收藏