addImageAsync異步加載
未響應回調前調用unbindImageAsync撤銷消息回調
void TextureCache::unbindImageAsync(const std::string& filename)
{
_imageInfoMutex.lock();
if (_imageInfoQueue && !_imageInfoQueue->empty())
{
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(filename);
auto found = std::find_if(_imageInfoQueue->begin(), _imageInfoQueue->end(), [&fullpath](ImageInfo* ptr)->bool{ return ptr->asyncStruct->filename == fullpath; });
if (found != _imageInfoQueue->end())
{
(*found)->asyncStruct->callback = nullptr;
}
}
_imageInfoMutex.unlock();
}
可是在極端狀況下,調用 addImageAsync後立刻調用unbindImageAsync
此時loadImage線程還未將ImageInfo加入_imageInfoQueue
而是在unbindImageAsync以後添加,致使消息回調解綁失敗php
沒法調用(*found)->asyncStruct->callback = nullptr;
從而在異步加載完成後調用callback時候出現問題
html
loadImage()線程
同時操做兩個鏈表
_asyncStructQueue
_imageInfoQueue
請求從_asyncStructQueue彈出後生成ImageInfo添加到_imageInfoQueue
void TextureCache::loadImage()
{
_asyncStructQueueMutex.lock();
_asyncStructQueue
_asyncStructQueueMutex.unlock();
.....臨界點,執行unbindImageAsync
_imageInfoMutex.lock();
_imageInfoQueue->push_back(imageInfo);
_imageInfoMutex.unlock();
}
此處的Bug是兩個鏈表是分別加鎖的
請求從_asyncStructQueue彈出後,未即時插入_imageInfoQueue
在臨界點又調用了unbindImageAsync撤銷異步加載響應
跟昨天提到的Bug相似,一樣會致使撤銷失敗
在回調時引發異常 安全
=================================================================異步
如下爲轉帖async
就以往的經驗,異步加載圖片是一個複雜的工做,每每容易出現bug。
那麼,cocos2d-x提供的這個異步加載功能是否可靠?百度了一下,沒發現什麼重要的信息,因而本身分析之。
照cocos2d-x自身的註釋來看,這個addImageAsync函數是從0.8版本就有了,而如今是3.1版本,怎麼也該穩定了吧?惋惜的是,裏面的陷阱並很多。
陷阱1:_textures未加鎖
在異步加載時,cocos2d-x主要用了兩個隊列,即_asyncStructQueue和_imageInfoQueue,在操做這兩個隊列的時候也都很當心的加鎖了。可是對_textures的訪問則沒有加鎖。所以,若是先用addImageAsync進行異步加載圖片A,再用addImage同步加載圖片B,則有概率致使_textures這個對象被損壞,進而致使程序不穩定。
修改:因爲涉及到_textures的地方不少,逐一加鎖實在麻煩,因此乾脆不加鎖,轉爲讓異步線程不要訪問_textures。大不了就是同一張圖片被屢次加載,浪費一些運算量罷了。上層代碼當心控制的話,是不會真的有圖片被重複加載的。
陷阱2:判斷邏輯錯誤
在cocos2d-x 3.1.1版本中,異步加載的代碼中有一句判斷:if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename)),這是有問題的。
做者的本意多是想,若是隊列中有多個請求都是加載同一幅圖片,那麼其實只須要加載一次便可。惋惜這個判斷寫反了,下文又有一處判斷寫反,致使不知所云。
這個問題在cocos2d-x 3.2版本已經修復了。
P.S. 字符串比較,仍是用==、!=這樣的操做符比較好,可讀性和運行性能都要優於compare函數。
修改:cocos官方已經修正。不過其實跟陷阱1相似,沒必要判斷,大不了就是同一張圖片被屢次加載,浪費一些運算量罷了。
陷阱3:insert失敗致使內存泄漏
在異步加載完畢以後,主線程有一句:_textures.insert( std::make_pair(filename, texture) );。
因爲陷阱一、陷阱2中,咱們並無進行完全的檢查,因此有概率出現重複加載的情形。(實際上,除非全程加鎖,不然很難完全避免重複加載。然而,全程加鎖的話,異步加載也就沒有意義了)。當出現重複加載同一張圖片時,這裏的insert就會失敗。因而,texture不會有被銷燬的機會,因而形成內存泄漏。
陷阱4:建立線程時,須要的變量還沒有初始化完畢
建立異步加載的線程時,原始代碼是先建立線程,再設置_needQuit。
正常應該是先設置_needQuit爲false(初始化值爲true!),再建立線程。不然理論上有可能線程剛建立完畢就當即結束。
疑似陷阱
異步加載線程和主線程,都調用了Image::initWithImageFileThreadSafe,這個函數看名字彷佛是線程安全的,實際上它調用了FileUtils::fullPathForFilename。這個函數除非參數是絕對路徑,不然就會對一個名爲_fullPathCache的哈希表進行讀寫,若不加鎖就會出錯。幸虧在異步加載線程中,傳入給FileUtils::fullPathForFilename的參數已是絕對路徑,因此沒有上述的問題。 函數