sqlite и многопоточность с iPhone SDK - PullRequest
2 голосов
/ 02 декабря 2010

У меня есть приложение для iPhone, которое использует sqlite 3.6 (не с FMDB) для хранения и загрузки данных.Я загружаю базу данных, когда приложение загружает и использует одно и то же соединение с базой данных через все приложение.

В фоновом потоке приложение загружает некоторые данные с веб-сервера и записывает их в базу данных.В то же время основной поток также может нуждаться в записи в ту же базу данных.Это иногда приводит к 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 ]

3 голосов
/ 06 декабря 2010

Я решил эту проблему, используя один поток и NSOperationQueue для вставки данных. Я бы подумал. Мне никогда не удавалось получить стабильную Систему с несколькими потоками, и большинство записей не настолько важны, чтобы организация очередей действительно помогла.

По запросу, еще немного информации:

У меня есть подкласс NSOperation, который я создаю с помощью объекта модели, который хочу сохранить. Эти операции передаются расширению NSOperationsQueue, которое выполняется в отдельном потоке. Эта пользовательская очередь просто добавляет указатель на экземпляр базы данных. Когда операция выполняется, она использует свойство [NSOperationsQueue currentQueue] для доступа к очереди, а затем к базе данных. Я специально использовал не параллельные операции (maxOperations был установлен в 1)
Следовательно, только один запрос (или обновление) выполняется одновременно, полностью в фоновом режиме.

Очевидно, вам нужен какой-то обратный вызов после того, как вы закончите

Возможно, это не самое быстрое, но самое стабильное и чистое решение, которое я мог найти.

Docs:
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 голосов
/ 20 ноября 2013

Как вы заметили, только один поток может одновременно обращаться к базе данных sqlite. Варианты предотвращения одновременного доступа:

  1. Создайте новое соединение с базой данных в каждом потоке и полагайтесь на блокировку файлов (дорого).
  2. Включить sqlite3_config (SQLITE_CONFIG_SERIALIZED).
  3. Используйте NSLock's.
  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 () для чтения).

1 голос
/ 22 августа 2011

Я пробовал эти два решения, и они отлично работали. Вы можете использовать критические разделы или 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];
}

эти два решения блокируют текущий поток до завершения записи в базу данных, что вы можете рассмотреть в большинстве случаев.

1 голос
/ 02 декабря 2010

Все это объясняется в Руководстве по программированию C ore * в разделе для параллелизма.

Шаблон, рекомендуемый для одновременного программирование с использованием Core Data является потоком ограничение.

Вы должны дать каждому потоку свой полностью частный управляемый объект контекст и сохранить их связанные графы объектов разделены на на основе потока.

Есть два возможных способа принятия шаблон:

Создать отдельный управляемый объект контекст для каждого потока и поделиться единый постоянный координатор магазина. Это обычно рекомендуемый подход.

Создать отдельный управляемый объект контекст и постоянный магазин координатор для каждой темы. это подход предусматривает большее параллелизм за счет большего сложность (особенно если вам нужно сообщать об изменениях между разные контексты) и увеличилась использование памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...