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 (сам)". Однако это не помогло.

У кого-либо есть подсказка?

7
задан marc_s 23 January 2016 в 09:34
поделиться

1 ответ

Вы должны добавить синхронизированную оболочку вокруг ваших функций 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

2
ответ дан 7 December 2019 в 05:17
поделиться