使用iPhone SDK进行sqlite和线程处理

时间:2010-12-02 17:41:48

标签: iphone multithreading sqlite

我有一个使用sqlite 3.6(不带FMDB)的iPhone应用程序来存储和加载数据。我在应用程序加载时加载数据库,并通过整个应用程序使用相同的数据库连接。

在后台线程中,应用程序从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;
}

4 个答案:

答案 0 :(得分: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 :(得分:2)

正如您所注意到的,一次只有一个线程可以访问sqlite数据库。防止同时访问的选项:

  1. 在每个线程中创建一个新的数据库连接,并依赖文件锁定(代价高昂)。
  2. 启用sqlite3_config(SQLITE_CONFIG_SERIALIZED)。
  3. 使用NSLock。
  4. 使用GCD(Grand Central Dispatch)队列。
  5. 前三个选项可能导致忙等待(一个线程等待另一个线程释放锁定),这是浪费。

    我使用选项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);
    

    在一个完美的世界中,sqlite可以让你同时执行读操作,但一次只能执行一次写操作(例如,使用dispatch_barrier_async()进行写操作,使用dispatch_async()进行读取)。

答案 2 :(得分:1)

在并发部分的C ore Data Programming Guide中对此进行了解释。

  

建议并发使用的模式   使用Core Data进行编程是线程   约束

     

你应该给每个线程都有自己的   完全私人管理对象   上下文并保持相关联   对象图分离   每线程基础。

     

有两种可能的采用方法   模式:

     

创建单独的托管对象   每个线程的上下文并共享一个   单个持久性存储协调器。   这是通常推荐的   方法

     

创建单独的托管对象   上下文和持久存储   每个线程的协调员。这个   方法规定更大   并发性以牺牲更大   复杂性(特别是如果你需要   沟通之间的变化   不同的背景)和增加   内存使用情况。

答案 3 :(得分: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...
    }
}

要使用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];
}

这两个解决方案会阻塞当前线程,直到完成对数据库的写入,在大多数情况下您可以考虑这些。