fmdb中databasequeue的使用,避免死鎖

openglnewbee發表於2014-05-21

在ios開發中,大家很可能會用到這樣一個資料庫封裝:fmdb.

該封裝相比coredata來說有他自己的優勢:介面清晰,設計簡單,符合規範,多執行緒情況下使用databasequeue來進行操作也很方便,還可以在其基礎上再進行一些封裝來方便專案的使用。


正是因為fmdb的簡單性,所以很容易被誤用。在我們的專案開發中就遇到了一例(我們專案中的程式碼進行了封裝,我這裡將其還原,寫示例來作說明):


  1. [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {    
  2.             [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];    
  3.             [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];    
  4.             [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];  
  5.     [
    queue
    inDatabase:^(FMDatabase *db) {
            // do work B
        }];

  6.  
  7.             if (whoopsSomethingWrongHappened) {    
  8.                     *rollback = YES; return;    
  9.             }   
  10.             // etc…    
  11.             [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];    
  12.     }];  

注意第6行往下,是不是混入了什麼奇怪的東西?

從這裡看起來問題非常明顯了,但實際專案中介面被進行了一些封裝,所以很難一眼看出問題,這裡的問題是這樣的:

在queue的事務內部又巢狀使用了該queue去執行任務b,而作為一個序列化的佇列來說必須要等該事務整個執行完畢才能執行任務b;此時任務b無法走下去,該事務也就無法執行完畢,導致了死鎖。

用一個比喻來說這個問題:一個人出門把門鎖上了,然後把鑰匙從門縫又塞回到家裡,這樣他就無法再進入家門了。

這個誤用其實很低階,而且從原理上講任何序列佇列裡面序列任務巢狀執行都有問題。


另外,fmdb的官方文件也多次提醒避免巢狀使用,請大家寫程式碼的時候一定要注意~





相關文章