iOS 多线程中使用SQLite

1、SQLite多线程

SQLite支持三种不同的线程模式:
①单线程(Single-thread)
该模式下,所有的互斥锁被禁用;SQLite在多线程中使用是不安全的。
当SQLite编译时加了SQLITE_THREADSAFE=0参数,或者在初始化SQLite前调用sqlite3_config(SQLITE_CONFIG_SINGLETHREAD)时启用该模式。

②多线程(Multi-thread)
这种模式下,SQLite在多线程中同时使用单个数据库连接是不安全的,否则就是安全的。(不能在多个线程中并发使用同一个数据库连接)。

③串行(Serialized)
在串行模式下,SQLite在多线程中使用是安全的。

线程模式可以在编译时(通过源码编译sqlite库时)、启动时(使用sqlite的应用程序初始化时)或者运行时(创建数据库连接时)来指定。一般而言,运行时指定的模式将覆盖启动时的指定模式,启动时指定的模式将覆盖编译时指定的模式。但是,单线程模式一旦被指定,将无法被覆盖。
默认的线程模式是串行模式。

编译时选择线程模式
可以通过定义SQLITE_THREADSAFE宏来指定线程模式:
SQLITE_THREADSAFE=1指定使用串行模式;
SQLITE_THREADSAFE=0使用单线程模式;
SQLITE_THREADSAFE=2使用多线程模式。
如果没有指定,默认为串行模式。
官方链接:Using SQLite In Multi-Threaded Applications

2、iOS平台的SQLite

在iOS平台上,默认使用的是第2种线程模式编译的(Multi-thread),也就是只有一个线程能够打开数据库操作,其他线程要操作数据库必须等数据库关闭后才能打开操作。多线程时:每个线程独立打开数据库,操作数据库,操作完后关闭数据库。打开和关闭都比较费时间,而且要手动控制打开关闭锁,在每个线程操作不频繁时可用该方法。
如果多个线程频繁操作数据库,使用以上方法很容易造成系统崩溃,解决方案:
①开启第3种串行模式,使用一个类(单例方式)操作数据库。
②使用串行队列操作数据库。

2.1 开启第3种串行模式,使用一个类(单例方式)操作数据库。

- (BOOL)open {
    if (_db) {
        return YES;
    }

    int err = sqlite3_config(SQLITE_CONFIG_SERIALIZED);
    if(err != SQLITE_OK) {
        NSLog(@"error configing!: %d", err);
        return NO;
    }

    err = sqlite3_open([self sqlitePath], (sqlite3**)&_db );
    if(err != SQLITE_OK) {
        NSLog(@"error opening!: %d", err);
        return NO;
    }

    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    }

    return YES;
}

2.2 使用串行操作队列操作数据库。

1、创建操作列队

- (id)initWithPath:(NSString *)path
{
    self = [super init];

    _path = path;
    _databaseQueue = [[NSOperationQueue alloc] init];
    [_databaseQueue setMaxConcurrentOperationCount:1];

     return self;
}

- (void)queueDatabaseOperation:(StorageOperation *)request
{
    [_databaseQueue addOperation:request];
}

2、封装操作对象
操作基类

@implementation StorageOperation

- (id)init {
    self = [super init];
    return self;
}


- (void)startRequest
{
    [_databaseController queueDatabaseOperation:self];
}


- (void)cancel
{
    [super cancel];
}


- (void)main
{
    [self mainRequest];
#if 1
    [self performSelectorOnMainThread:@selector(_finished) withObject:nil waitUntilDone:NO];
#else
    [self _finished];
#endif
}


- (void)mainRequest
{
}

- (void)mainFinished
{
}

- (void)_finished {
    [self mainFinished];
}

@end
具体的操作(子类)
#import "StorageOperation.h"

@interface UpdateMultiMessageContentOperation : StorageOperation

@property (nonatomic, copy) NSString * messagaId;

@property (nonatomic, copy) NSString * messagaContent;

- (void)start:(void(^)(void))callback;
@end


#import "UpdateMultiMessageContentOperation.h"
#import "Database+message.h"

@implementation RCSUpdateMultiMessageContentOperation
{
    BOOL ret;
    void(^_callback)(void);
}

- (void)start:(void(^)(void))callback
{
    _callback = callback;
    [super startRequest];
}

- (void)mainRequest
{
    [self.database beginTransaction];
    [self.database updateMultiMessageContentByMessageId:self.messagaId messageContent:self.messagaContent];
    [self.database commit];
}

- (void)mainFinished
{
    if (_callback)
    {
        _callback();
    }
}

@end

2.3 分析FMDB多线程操作实现方式。

FMDatabaseQueue.h文件描述:
To perform queries and updates on multiple threads, you'll want to use `FMDatabaseQueue`.
Using a single instance of `<FMDatabase>` from multiple threads at once is a bad idea.  It has always been OK to make a `<FMDatabase>` object *per thread*.  Just don't share a single instance across threads, and definitely not across multiple threads at the same time.
Instead, use `FMDatabaseQueue`. Here's how to use it:

 First, make your queue.
  FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
 Then use it like so:
 [queue inDatabase:^(FMDatabase *db) {
   [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
   [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
   FMResultSet *rs = [db executeQuery:@"select * from foo"];
       while ([rs next]) {
       //…
      }
 }];
 An easy way to wrap things up in a transaction can be done like this:
  [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
     if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
     }
     // etc…
      [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];
  }];
`FMDatabaseQueue` will run the blocks on a serialized queue (hence the name of the class).  So if you call `FMDatabaseQueue`'s methods from multiple threads at the same time, they will be executed in the order they are received.  This way queries and updates won't step on each other's toes, and every one is happy.

 ### See also
  Do not instantiate a single `<FMDatabase>` object and use it across multiple threads. Use `FMDatabaseQueue` instead.
 The calls to `FMDatabaseQueue`'s methods are blocking.  So even though you are passing along blocks, they will **not** be run on another thread.

原理:
创建串行队列,在串行队列中执行查询和更新操作。

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/xuhen/article/details/78967846