MST

星途 面试题库

面试题:SQLite数据类在iOS多线程环境下的设计与实现

在iOS多线程环境中使用SQLite数据库,并为其数据创建类。请详细说明如何设计这些类以确保线程安全,包括如何处理并发访问数据库、锁机制的选择与应用,以及如何避免数据竞争和死锁等问题。
37.3万 热度难度
数据库SQLite

知识考点

AI 面试

面试题答案

一键面试

1. 设计数据库管理类

  1. 单例模式:创建一个数据库管理类,采用单例模式确保整个应用程序只有一个数据库实例,便于统一管理。例如:
@interface DatabaseManager : NSObject

@property (nonatomic, strong) FMDatabase *database;

+ (instancetype)sharedManager;

@end

@implementation DatabaseManager

static DatabaseManager *sharedInstance = nil;

+ (instancetype)sharedManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:@"your_database_name.db"];
        sharedInstance.database = [FMDatabase databaseWithPath:databasePath];
        if (![sharedInstance.database open]) {
            NSLog(@"Error opening database: %@", sharedInstance.database.lastError);
        }
    });
    return sharedInstance;
}

@end
  1. 线程安全的方法:在数据库管理类中,所有对数据库的操作方法都应该是线程安全的。比如查询、插入、更新和删除操作。

2. 锁机制

  1. 选择合适的锁
    • pthread_mutex:这是一种简单且高效的C语言级别的互斥锁。可以通过pthread_mutex_init初始化,pthread_mutex_lock锁定,pthread_mutex_unlock解锁。例如:
pthread_mutex_t databaseMutex;
pthread_mutex_init(&databaseMutex, NULL);
// 在数据库操作前
pthread_mutex_lock(&databaseMutex);
// 执行数据库操作,如插入数据
[database executeUpdate:@"INSERT INTO your_table (column1, column2) VALUES (?,?)", value1, value2];
// 操作完成后
pthread_mutex_unlock(&databaseMutex);
- **`dispatch_semaphore`**:基于GCD的信号量机制,在iOS开发中使用方便。例如:
dispatch_semaphore_t databaseSemaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(databaseSemaphore, DISPATCH_TIME_FOREVER);
// 执行数据库操作
[database executeQuery:@"SELECT * FROM your_table"];
dispatch_semaphore_signal(databaseSemaphore);
  1. 锁的应用范围:将锁应用在对数据库进行读写操作的代码段,确保同一时间只有一个线程能够访问数据库。

3. 避免数据竞争和死锁

  1. 数据竞争
    • 统一访问入口:通过前面提到的单例数据库管理类,确保所有对数据库的访问都经过该类的方法,在方法内部加锁,避免多个线程直接操作数据库导致数据竞争。
    • 合理安排操作顺序:对于涉及多个数据库操作的事务,按照固定的顺序进行操作,避免不同线程以不同顺序操作数据库导致数据竞争。
  2. 死锁
    • 避免嵌套锁:尽量避免在持有一个锁的情况下尝试获取另一个锁,特别是在多个线程中以不同顺序获取锁的情况。如果确实需要嵌套锁,要确保所有线程获取锁的顺序一致。
    • 设置超时:在获取锁时设置超时时间,例如使用dispatch_semaphore_wait时,可以设置一个DISPATCH_TIME_NOW加上一个时间间隔,如果在规定时间内未能获取锁,则放弃操作并处理相应的错误,防止线程无限期等待。例如:
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
long result = dispatch_semaphore_wait(databaseSemaphore, timeout);
if (result == 0) {
    // 获取锁成功,执行数据库操作
    [database executeUpdate:@"UPDATE your_table SET column1 =? WHERE id =?", value, idValue];
    dispatch_semaphore_signal(databaseSemaphore);
} else {
    // 获取锁超时,处理错误
    NSLog(@"Failed to acquire semaphore within timeout");
}

4. 数据库操作类

  1. 封装数据库操作:创建具体的数据库操作类,例如UserDatabaseOperation,将对用户表的操作封装在该类中。每个操作方法内部调用数据库管理类的方法,并在合适的位置加锁。
@interface UserDatabaseOperation : NSObject

+ (BOOL)insertUser:(User *)user;
+ (User *)getUserById:(NSInteger)userId;

@end

@implementation UserDatabaseOperation

+ (BOOL)insertUser:(User *)user {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    BOOL success = [[DatabaseManager sharedManager].database executeUpdate:@"INSERT INTO users (name, age) VALUES (?,?)", user.name, @(user.age)];
    dispatch_semaphore_signal(semaphore);
    return success;
}

+ (User *)getUserById:(NSInteger)userId {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    FMResultSet *resultSet = [[DatabaseManager sharedManager].database executeQuery:@"SELECT name, age FROM users WHERE id =?", @(userId)];
    User *user = nil;
    if ([resultSet next]) {
        user = [[User alloc] init];
        user.name = [resultSet stringForColumn:@"name"];
        user.age = [resultSet intForColumn:@"age"];
    }
    [resultSet close];
    dispatch_semaphore_signal(semaphore);
    return user;
}

@end

5. 数据模型类

  1. 定义数据模型:创建与数据库表结构对应的模型类,例如User类,用于封装从数据库中读取的数据或准备插入数据库的数据。
@interface User : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

@implementation User

@end