做客戶端開發應當時刻考慮多執行緒問題。我最初是做前端開發的,在這方面考慮得往往不夠。謹記。
單例的常見寫法
單例的常見寫法其實就兩種
1. 依賴鎖
+ (id)sharedInstance {
static testClass *sharedInstance = nil;
@synchronized(self) {
if (!sharedInstance) {
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
2. 依賴dispatch_once
+ (id)sharedInstance {
static testClass *sharedInstance = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
dispatch_once的寫法更推薦一些。一方面是效能上好一點,另一方面是語義上更直觀。once,執行一次嘛。
不管是用鎖還是dispatch_once,本質上都是為了避免單例建立過程出現執行緒安全問題。
更進一步,我們經常會有懶載入某些屬性的寫法:
- (id<InterfaceEngineA>)engineA
{
if (_engineA == nil) {
_engineA = [EngineA new];
}
return _engineA;
}
其實跟單例的實現是類似的,這種時候要格外注意執行緒安全問題。如果存在多執行緒場景,一定要做好保護
- (id<InterfaceEngineA>)engineA
{
@synchronized(self) {
if (_engineA == nil) {
_engineA = [EngineA new];
}
}
return _engineA;
}
一些廢話
多執行緒問題的表現可能是各種各樣難以預料的。這裡我遇到的是,_engineA在多執行緒場景下小概率被重複建立,其例項1在init時註冊了網路層命令字cmd1
的回包,而這個網路層框架的實現是,只接受第一個註冊這一命令字的物件。導致例項2註冊失敗。後面呼叫例項2傳送請求,回包都被例項1接收了。從日誌上看,一切都挺正常的。但是下次取資料就是取不到。
這個bug第一次提過來的時候,沒分析出根本原因,只在表面上做了保護。結果第二次提過來才真正改掉。
丟人吶。還是要好好學習才是。