模式可在编译,启动时或运行时设置,一般来讲,启动时的设置会覆盖编译时的设置,运行时则会覆盖启动时或编译时的设置。但是一旦单线程模式被设置之后就不能再被覆盖了。
编译时可通过SQLITE_THREADSAFE设置模式。sqlite3标准 发行版本默认设置为SQLITE_THREADSAFE=1, 即串行模式。SQLITE_THREADSAFE=2为多线程模式,SQLITE_THREADSAFE=0为单线程模式,当设置为0时,即使初始化或运行时设置为其他模式,也会保持单线程模式。
启动时是指在sqlite3在初始化之前调用sqlite3_config()函数设置,参数为SQLITE_CONFIG_SINGLETHREAD,SQLITE_CONFIG_MULTITHREAD或SQLITE_CONFIG_SERIALIZED。
sqlite3初始化指的是调用sqlite3_initalize(),该函数会在调用sqlite3_open_v2()时调用。运行时也就是程序第一次(仅第一次调用有效)调用该函数创建数据库连接时,通过设置第三个参数为设置不同模式,SQLITE_OPEN_NOMUTEX为多线程模式,SQLITE_OPEN_FULLMUTEX为串行模式。
串行模式支持多线程操作,但是必须统一使用一个全局的数据库连接,这一点非常重要。串行模式会打开sqlite3所有的锁,在同一时刻保证只有一个线程能访问。这里可以理解为只有一条指向数据库的连接,多个线程的请求将会在该连接上串行传输。
多线程模式支持线程并发操作,但也有例外,因为在该模式下,sqlite3打开了bCoreMutex锁,关闭了bFullMutex锁,也就禁止了多个线程并发使用同一个数据库连接和perpared statement, perpared statement可以简单的使用信号量进行互斥。
使用多线程模式,除了需要设置模式外,还必须在执行数据库操作前调用sqlite3_busy_handler()或sqlite3_busy_timeout() 。这两个函数会判断sqlite是否处于SQLITE_BUSY状态,是的话将进行sleep等待。区别在于sqlite3_busy_handler()需要自己实现等待的处理,而sqlite3_busy_timeout()实际上是调用了sqlite3_busy_handler()并使用一个默认的等待处理函数。
sqlite3_busy_timeout()通过指定最大超时时间进行等待。根据sqlite3_busy_timeout()源码,需要注意,如果系统未定义HAVE_USLEEP或定定义为false,则超时时间必须指定为大于1000且是它的整数倍。
无论有无定义,使用sqlite3_busy_timeout()的等待时间都是该函数的算法实现,所以如果需要完全按照自己的需求决定等待时间,可以使用sqlite3_busy_handler(),该函数需要自己实现等待处理函数,处理函数中即可指定等待时间。
sqlite3_busy_timeout() 源码:
static int sqliteDefaultBusyCallback(
void *ptr, /* Database connection */
int count /* Number of times table has been busy */
)
{
#if SQLITE_OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP)
static const u8 delays[] =
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
static const u8 totals[] =
{ 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
# define NDELAY (sizeof(delays)/sizeof(delays[0]))
sqlite3 *db = (sqlite3 *)ptr;
int timeout = db->busyTimeout;
int delay, prior;
assert( count>=0 );
if( count < NDELAY ){
delay = delays[count];
prior = totals[count];
}else{
delay = delays[NDELAY-1];
prior = totals[NDELAY-1] + delay*(count-(NDELAY-1));
}
if( prior + delay > timeout ){
delay = timeout - prior;
if( delay<=0 ) return 0;
}
sqlite3OsSleep(db->pVfs, delay*1000);
return 1;
#else
sqlite3 *db = (sqlite3 *)ptr;
int timeout = ((sqlite3 *)ptr)->busyTimeout;
if( (count+1)*1000 > timeout ){
return 0;//1000>timeout,so timeout must bigger than 1000
}
sqlite3OsSleep(db->pVfs, 1000000);//1000ms
return 1;
#endif
}
int sqlite3_busy_timeout(sqlite3 *db, int ms){
if( ms>0 ){
db->busyTimeout = ms;
sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
}else{
sqlite3_busy_handler(db, 0, 0);
}
return SQLITE_OK;
}
// 判断编译时模式,设置启动时模式为多线程模式
int mode = sqlite3_threadsafe();
if (mode == 0) {
AIO_LOG_W("SQLite database is not compiled to be threadsafe");
return AIO_ERR;
}
int ret = sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
if (ret != SQLITE_OK) {
AIO_LOG_E("setting sqlite thread safe mode to multithread failed: %d", ret);
return AIO_ERR;
}
... ...
// 设置SQLITE_BUSY 处理,使用sqlite3_busy_timeout()
#define SQLITE_EXE(db, sql, callback, ptr, msg) \
{ \
rc = sqlite3_busy_timeout(db, 10 * MSEC_PER_SEC); \
if (rc != SQLITE_OK) { \
AIO_LOG_E("SQL error: %d, file: %s. func: %s, line: %d.\n", \
rc, __FILE__, __func__, __LINE__); \
} \
rc = sqlite3_exec(db, sql, callback, ptr, &msg); \
if (rc != SQLITE_OK) { \
AIO_LOG_E("SQL error: %s, file: %s. func: %s, line: %d.\n", \
msg, __FILE__, __func__, __LINE__); \
sqlite3_free(msg); \
} \
}
// 设置SQLITE_BUSY 处理,使用sqlite3_busy_handler()
static int cb_sql_busy(void *ptr, int count)
{
int timeout = *((int *)ptr);
(void)usleep(timeout);
return 1;
}
static int sql_busy_check(sqlite3 *db, int ms)
{
if( ms > 0 )
sqlite3_busy_handler(db, cb_sql_busy, (void*)&ms);
else
sqlite3_busy_handler(db, 0, 0);
return SQLITE_OK;
}
int my_sqlite_exe(...) {
rc = sql_busy_check(db, 100);
... ...
rc = sqlite3_exec(db, sql, NULL, NULL, &msg);
... ...
}