2010-02-24 3 views
5

Я работаю над iPhone-приложением, которое использует базу данных sqlite. Приложение загружает данные из Интернета в фоновый поток с пользовательским интерфейсом в основном потоке. В потоке загрузки фона можно занести в базу данных INSERT, UPDATE и SELECT. Уровень пользовательского интерфейса также может взаимодействовать с базой данных, выполняя UPDATE и SELECT. Если я не сильно взаимодействую с пользовательским интерфейсом, когда загружается фоновый поток, все работает нормально. Тем не менее, я начинаю сталкиваться с проблемами, когда во время загрузки происходит много UPDATE в основном (UI) потоке.sqlite проблемы параллелизма

Приложение всегда завершает работу, когда пытается запустить функцию базы данных. Он завершает работу с EXC_BAD_ACCESS, и я не вижу никаких ошибок. Например, в последний раз он бросил это закончилось в sqlite3_step:

sqlite3_stmt *statement; 
const char *query = "INSERT OR IGNORE INTO `names` (`id`,`name`) VALUES (?,?);"; 
if(sqlite3_prepare_v2(database, query, -1, &statement, NULL) != SQLITE_OK){ 
    NSAssert1(0, @"Error while creating insert statement. '%s'", sqlite3_errmsg(database)); 
    return NO; 
} 
sqlite3_bind_int(statement, 1, id); 
sqlite3_bind_text(statement, 2, name, -1, SQLITE_TRANSIENT); 

if(sqlite3_step(statement) != SQLITE_DONE) 
    NSAssert1(0, @"Error while inserting. '%s'", sqlite3_errmsg(database)); 

sqlite3_finalize(statement); 

Это не всегда бросить на sqlite3_step, иногда он выходит на sqlite3_prepare_v2 или sqlite3_exec. Я пытался поставить эти заявления в петлю и повторите попытку, если она не возвращается в порядке, но это не работает, либо:

int returnCode = 0; 
do{ 
    returnCode = sqlite3_step(statement); 
    if(returnCode != SQLITE_DONE){ 
     usleep(20); 
    } 
}while(returnCode != SQLITE_DONE); 

Я также попытался транзакции SQL, но это не имеет никакого значения , Как я могу это решить? Похоже, что это довольно простая проблема параллелизма, но я не видел ничего, что работает для меня.

Спасибо за вашу помощь, Джастин

+0

приложение * * квиты? Просто быть любопытным, это просто какая-то конкретная сборка Apple? В ванильном Sqlite он просто возвращает ошибку SQLITE_LOCKED, если есть конфликт, и вы также можете установить обработчик занятости для принятия решений о повторных попытках в этих случаях. –

ответ

1

Я нахожусь в процессе написания программы в Objective-C, который почти идентичен поведение w.r.t.

Вот как я намерен синхронизации доступа (Вопрос, который я спросил есть своего рода связаны между собой, но посмотрите на код):

Calling sqlite3_close for a static sqlite3* handle

Я собираюсь использовать статический экземпляр NSLock и заблокируйте его во время записи, а затем откройте его, когда закончите.

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

2

Если вы не перекомпилируете его со специальной настройкой, SQLite не является потокобезопасным.

См http://www.sqlite.org/faq.html#q6

Так что до вас, чтобы заботиться о доступе к БД и вызова операции SQL на ней из той же нити.

Тем не менее, я придумал решение на моей стороне, которое, кажется, подходит даже в многопоточной среде: я гарантирую, что любая операция SQLite защищена директивой @synchronized, чтобы гарантировать, что когда поток что-то делает на DB, любой другой поток не может получить доступ к нему.

Итак, вместо того, чтобы говорить, что «все операции SQlite должны выполняться в одном потоке», я бы сказал: «Убедитесь, что две операции не выполняются параллельно в разных потоках».

0

У меня была такая же проблема в моем приложении, которая работает примерно одинаково. Всякий раз, когда поток, обновляющий данные из Интернета, начал записывать в базу данных одновременно с тем, что я сделал некоторое взаимодействие с пользователем, которое вызвало доступ к БД, программа потерпела крах.

@ Синхронизированные утверждения по каждому запросу базы данных внутри моего DB-обработчика, похоже, решают проблему.

1

Я не уверен, что это действительное решение, но то, что я сделал бы, это загрузить все данные в отдельный поток. Но когда это будет сделана загрузка, вернитесь к основному потоку и сделайте свои вставки в основном потоке.

dispatch_async(dispatch_get_global_queue(0, 0), ^{ 

    //download data from internet 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     //update database here 
    } 
} 

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

0

Начиная с версии 3.5.0 вы можете использовать одно и то же соединение с базой данных из нескольких потоков: http://www.sqlite.org/34to35.html Проверьте версию SQLite, которую вы используете.

Также проверьте функцию sqlite3_threadsafe.

Я написал программу на C++, которая разделяет соединение с базой данных между двумя потоками и не получает seg-сбоев (я считаю, что это то же самое, что и EXC_BAD_ACCESS): https://gist.github.com/allyourcode/7428159 В этом примере показано использование базы данных в памяти, но я получаю схожую результаты с резервной базой данных.

Я хотел бы проанализировать это с помощью инструмента гонки данных, таких как Цзаня, но мне нужно, чтобы выяснить, как это сделать: P

Смежные вопросы