2010-12-02 75 views
3

我有一個iPhone應用程序正在使用sqlite 3.6(不與FMDB)來存儲和加載數據。當應用程序加載並通過整個應用程序使用相同的數據庫連接時,我加載數據庫。sqlite和線程與iPhone SDK

在後臺線程中,應用程序從Web服務器下載一些數據並寫入數據庫。同時主線程也可能需要寫入相同的數據庫。這有時會導致EXC_BAD_ACCESS,因爲兩個線程都試圖訪問數據庫。

什麼是能夠從不同線程使用數據庫的最好和最簡單的方法?

這是顯示問題的例子:

sqlite3 *database; 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; 
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"database.db"]; 

    if (sqlite3_open([path UTF8String], &database) != SQLITE_OK) { 
     sqlite3_close(database); 
     return YES; 
    } 

    [NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil]; 
    [self test]; 
    return YES; 
} 

-(void)test { 
    for (int i = 0; i < 2000; i++) { 
     NSLog(@"%i",i); 
     sqlite3_exec([self getDb],"UPDATE mytable SET test=''", 0, 0, 0); 
    } 
} 

編輯:

後willcodejavaforfood的答案在下面,我試圖改變我的代碼使用單獨的數據庫對象(連接)的每個單獨的線程並且還添加了sqlite3_busy_timeout(),這樣sqlite將在數據庫繁忙時重試寫入。現在我不再獲得EXC_BAD_ACCESS,但我注意到並非所有數據都被插入。所以這也不是一個穩定的解決方案。這似乎是真的很難得的SQLite與穿線工作..

我與獨立連接新的解決方案:

-(void)test { 
    sqlite3 *db = [self getNewDb]; 
    for (int i = 0; i < 2000; i++) { 
     NSLog(@"%i",i); 
     sqlite3_exec(db,"UPDATE mytable SET test=''", 0, 0, 0); 
    } 
} 

- (sqlite3 *)getNewDb { 
    sqlite3 *newDb = nil; 
    if (sqlite3_open([[self getDbPath] UTF8String], &newDb) == SQLITE_OK) { 
     sqlite3_busy_timeout(newDb, 1000); 
    } else { 
     sqlite3_close(newDb); 
    } 
    return newDb; 
} 

回答

3

我通過使用一個線程和一個NSOperationQueue插入數據來解決此問題。我會給它一些想法。我從來沒有能夠用多線程獲得穩定的系統,而且大多數寫入並不重要,排隊真的有幫助。

根據要求,一些相關信息:

我有NSOperation,我與我想要存儲模型對象實例化一個子類。 這些操作不是提交給在單獨線程中運行的NSOperationsQueue的擴展。這個自定義隊列只是添加一個指向數據庫實例的指針。執行操作時,它使用[NSOperationsQueue currentQueue]屬性訪問隊列和數據庫。有意使用非併發操作(maxOperations設置爲1)
因此,只有一個查詢(或更新)在連續的時間連續執行,完全在後臺執行。

顯然你完成後需要某種回調。

它可能不是最快,但我能找到的最穩定和最清潔的解決方案。

文檔:
http://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
http://icodeblog.com/2010/03/04/iphone-coding-turbo-charging-your-apps-with-nsoperation/

1

這一切都是在C ore Data Programming Guide在併發部分解釋。

推薦用於併發 與Core Data編程的模式是線程 約束。

你應該爲每個線程提供自己的 完全私人管理的對象 背景下,並保持在 每個線程分離及其關聯的 對象圖。

有兩種可能的方式來採用 模式:

爲每個線程創建一個單獨的管理對象 上下文和共享 單持久存儲協調。 這是通常推薦的 方法。

爲每個線程創建一個單獨的管理對象 上下文和持久存儲 協調程序。這種方法提供了更大的併發性,其代價是更大的複雜性(特別是如果您需要 在不同的上下文之間傳遞 之間的更改)和增加內存使用量時。

+0

感謝您的回答!但是,在所有線程和SQLITE_OPEN_FULLMUTEX(SQLite,序列化模式)之間使用一個連接有什麼問題?就像我以前問過的那樣,有什麼缺點? – mrrmatinsi 2010-12-02 18:45:48

+0

您是否閱讀過指南? – willcodejavaforfood 2010-12-02 18:48:44

1

我已經試過這兩種解決方案,他們的工作完美。您可以使用臨界區或NSOperationQueue,我更喜歡第一個,這裏是他們兩人的代碼:

定義一些類「DatabaseController」這個代碼添加到它的實現:

static NSString * DatabaseLock = nil; 
+ (void)initialize { 
    [super initialize]; 
    DatabaseLock = [[NSString alloc] initWithString:@"Database-Lock"]; 
} 
+ (NSString *)databaseLock { 
    return DatabaseLock; 
} 

- (void)writeToDatabase1 { 
    @synchronized ([DatabaseController databaseLock]) { 
     // Code that writes to an sqlite3 database goes here... 
    } 
} 
- (void)writeToDatabase2 { 
    @synchronized ([DatabaseController databaseLock]) { 
     // Code that writes to an sqlite3 database goes here... 
    } 
} 

OR使用NSOperationQueue你可以使用:

static NSOperationQueue * DatabaseQueue = nil; 
+ (void)initialize { 
    [super initialize]; 

    DatabaseQueue = [[NSOperationQueue alloc] init]; 
    [DatabaseQueue setMaxConcurrentOperationCount:1]; 
} 
+ (NSOperationQueue *)databaseQueue { 
    return DatabaseQueue; 
} 

- (void)writeToDatabase { 
    NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil]; 
    [operation setQueuePriority:NSOperationQueuePriorityHigh]; 
    [[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES]; 
    [operation release]; 
} 

這兩種解決方案阻止當前線程,直到寫入數據庫完成後,你可以在大多數的情況下考慮。

2

正如你所注意到的,一次只有一個線程可以訪問sqlite數據庫。防止同時訪問的選項:

  1. 在每個線程中創建新的數據庫連接並依賴文件鎖定(代價高昂)。
  2. 打開sqlite3_config(SQLITE_CONFIG_SERIALIZED)。
  3. 使用NSLock的。
  4. 使用GCD(Grand Central Dispatch)隊列。

前三個選項可能會導致繁忙的等待(一個線程在等待另一個線程釋放鎖),這是浪費。

我使用選項4,因爲它簡化了創建新查詢以在後臺運行並且沒有忙等待的任務。它還確保所有查詢都按照他們添加的順序執行(我的代碼傾向於假設)。

dispatch_queue_t _queue = dispatch_queue_create("com.mycompany.myqueue", DISPATCH_QUEUE_SERIAL); 

// Run a query in the background. 
dispatch_async(_queue, ^{ 

    ...some query 

    // Perhaps call a completion block on the main thread when done? 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     //completion(results, error); 
    }); 
}); 

// Run a query and wait for the result. 
// This will block until all previous queries have finished. 
// Note that you shouldn't do this in production code but it may 
// be useful to retrofit old (blocking) code. 
__block NSArray *results; 

dispatch_sync(_queue, ^{ 

    results = ... 
}); 

...use the results 

dispatch_release(_queue); 

在一個完美的世界源碼將讓你同時執行一次讀取,但只有一個寫操作(例如,喜歡使用dispatch_barrier_async()進行寫入和dispatch_async()進行讀取)。