【问题标题】:sqlite and threading with iPhone SDK使用 iPhone SDK 的 sqlite 和线程
【发布时间】:2013-11-22 12:10:01
【问题描述】:

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

在后台线程中,应用程序从网络服务器下载一些数据并写入数据库。同时主线程也可能需要写入同一个数据库。这有时会导致 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;
}

【问题讨论】:

    标签: iphone multithreading sqlite


    【解决方案1】:

    我通过使用一个线程和一个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/

    【讨论】:

    • 您能否尝试更详细地解释一下您是如何通过这种方式解决的?谢谢!
    • 再长一点。如果您需要更多详细信息,请查看我添加的文档
    • 感谢您的回答!看来我有很多工作要做才能让它发挥作用..
    【解决方案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);
    

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

    【讨论】:

      【解决方案3】:

      这在并发部分的 Core Data Programming Guide 中都有解释。

      推荐并发的模式 使用 Core Data 编程是线程 禁闭。

      你应该给每个线程自己的 完全私有的托管对象 上下文并保持关联 在 a 上分隔的对象图 基于每个线程。

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

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

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

      【讨论】:

      • 感谢您的回答!但是在所有线程和 SQLITE_OPEN_FULLMUTEX(SQLite,序列化模式)之间使用一个连接有什么问题?就像我之前问的那样,缺点是什么?
      • 在稍微重写了我的代码之后,我现在已经编辑并更新了我的问题。您认为这是一个更好的解决方案吗?
      • @mrrmatinsi - 你还有这个例外吗?
      • @willcodejavaforfood - 没有例外,但我注意到如果我有 2-3 个线程同时向数据库插入大量数据,则不会插入所有数据。也许有更好的方法来使它更稳定?将 sqlite3_busy_timeout 设置为间隔时,它会循环并在间隔后重试几次还是只重试一次?谢谢!
      • 我得到了非常不可靠的结果 - 但你的里程可能会有所不同
      【解决方案4】:

      我已经尝试了这两种解决方案,并且效果很好。您可以使用临界区或 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];
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-01
        • 1970-01-01
        • 2010-11-08
        • 2011-05-22
        • 2010-12-05
        • 2011-08-23
        • 2011-08-30
        • 2010-10-06
        相关资源
        最近更新 更多