EXC_BAD_ACCESS при использовании SQLite (FMDB) и потоков в iOS 4.0

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

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

Вот смотрю:

- (BOOL)isDatabaseLocked {
    return isDatabaseLocked;
}

- (Pile *)lockDatabase {
    isDatabaseLocked = YES;
    return self;        
}

- (FMDatabase *)lockedDatabase {
    @synchronized(self) {
        while ([self isDatabaseLocked]) {
            usleep(20);
            //NSLog(@"Waiting until database gets unlocked...");
        }
        isDatabaseLocked = YES;
        return self.database;       
    }
}

- (Pile *)unlockDatabase {
    isDatabaseLocked = NO;
    return self;            
}

Отладчик говорит, что ошибка возникает на [FMResultSet next] в строке

rc = sqlite3_step(statement.statement);

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

Моя последняя идея заключалась бы в том, чтобы оба потока запускали lockedDatabase одновременно, чтобы они могли получить объект базы данных. Вот почему я добавил блокировку мьютекса через «@synchronized(self)». Однако это не помогло.

Кто-нибудь знает?


person danielkbx    schedule 29.06.2010    source источник
comment
Эта тема для проблемы FMDB дает некоторые другие полезные сведения о возможных причинах: github.com/ccgus/fmdb /вопросы/39   -  person Fitter Man    schedule 17.04.2015


Ответы (4)


Вы должны добавить синхронизированную оболочку вокруг ваших функций unlockDatabase и lockDatabase, а также isDatabaseLocked — не всегда гарантируется, что сохранение или извлечение переменной является атомарным. Конечно, если вы это сделаете, вы захотите переместить свой сон за пределы синхронизированного блока, иначе вы зайдете в тупик. По сути, это спин-блокировка — не самый эффективный метод.

- (FMDatabase *)lockedDatabase {
    do
    {
        @synchronized(self) {
            if (![self isDatabaseLocked]) {
                isDatabaseLocked = YES;
                return self.database; 
            }
        }
        usleep(20);      
    }while(true); // continue until we get a lock
}

Убедитесь, что вы не используете объект FMDatabase после вызова unlockDatabase? Возможно, вы захотите рассмотреть шаблон дескриптора — создайте объект, который обертывает объект FMDatabase и, пока он существует, удерживает блокировку базы данных. В init вы запрашиваете блокировку, а в Dealloc вы можете снять эту блокировку. Тогда вашему клиентскому коду не нужно беспокоиться о вызове различных функций блокировки/разблокировки, и вы случайно не облажаетесь. Попробуйте использовать NSMutex вместо блоков @synchronized, см. http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW16

person Nicholas M T Elliott    schedule 29.06.2010
comment
Я нашел фрагмент кода, в котором я напрямую обращаюсь к базе данных. Так и не заблокировал. После исправления этой маленькой проблемы все работает отлично. - person danielkbx; 30.06.2010
comment
Как вы решили эту проблему? Можете ли вы опубликовать код, который помог? - person Split; 23.06.2011
comment
Я использую код, который я изначально опубликовал. Моя ошибка заключалась в том, что приложение сделало запрос к базе данных без блокировки, поэтому без вызова LockedDatabase. - person danielkbx; 15.02.2012

SQLite обеспечивает гораздо более простую сериализацию. Просто установив параметр sqlite_config() SQLITE_CONFIG_SERIALIZED, вы, вероятно, избежите большинства подобных головных болей. Я обнаружил это на собственном горьком опыте после долгой борьбы с проблемами потоковой передачи.

Вот как вы его используете, вы можете поместить его в метод инициализации FMDatabase...

    if (sqlite3_config(SQLITE_CONFIG_SERIALIZED) == SQLITE_ERROR) {
        NSLog(@"couldn't set serialized mode");
    }

См. документацию SQLite по безопасности потоков и последовательный режим для получения дополнительной информации.

person Rik Smith-Unna    schedule 29.07.2012
comment
СПАСИБО! Спасибо Спасибо спасибо. Это именно то, что мне было нужно! - person DOOManiac; 17.11.2012
comment
В документации SQLite говорится, что по умолчанию используется сериализованный режим, но похоже, что версия библиотек, поставляемых с MacOS, по умолчанию настроена на однопоточный режим (что я обнаружил на собственном горьком опыте, перенося программу, которая отлично работала на Linux и Windows на Mac). - person Head Geek; 09.02.2013
comment
Ребята, вы можете мне объяснить одну вещь? SQLITE_CONFIG_SERIALIZED гарантирует включение мьютекса внутри sqlite, круто. Значит, все его методы атомарны, верно? Но кто может гарантировать, что в клиентских методах в параллельной очереди все пойдет правильно? Например, мы вызываем void foo() { sqlite3_open() sqlite3_exec() sqlite3_next_stmt() sqlite3_finalize() sqlite3_close() } Каждый вызов внутри метода является атомарным и безопасным внутри. Но когда мы вызываем 'foo' из разных потоков, методы вызываются хаотично. Например, «завершить» из потока 1 после «открыть» из потока 2 и так далее. - person Vladimir Vlasov; 25.01.2017
comment
Да, sqlite3_open каждый раз возвращает разные экземпляры. Но на самом деле у меня есть 100% случай, когда наше приложение падает, если я удаляю @synchronized из метода, который использует sqlite. И да, это может быть не связано с sqlite, и я собираюсь исследовать этот вопрос подробнее. - person Vladimir Vlasov; 25.01.2017

Вы также можете попробовать FMDatabaseQueue — я создал его специально для таких ситуаций. Я не пробовал, но я уверен, что это будет работать для iOS 4.

person ccgus    schedule 19.05.2012
comment
Я думаю, что FMDatabaseQueue работает только для запросов, верно? Есть ли простой способ сделать то же самое для обновлений (кроме настройки SQLite выше)? - person Rik Smith-Unna; 29.07.2012

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

FMDatabase *myDatabase = [FMDatabase databaseWithPath: pathToDatabase];
myDatabase.shouldCacheStatements = YES;
person Fitter Man    schedule 17.04.2015