互斥锁是为了保证在多线程时一些不可重入函数执行的串行化,有些函数如malloc等会操作一些共享数据,如果被重入了就会导致共享资源被破坏,从而出现逻辑错误,所以如果有多个线程对共享资源的访问就要加互斥锁。
互斥锁部分的代码还是比较简单的,代码实现在mutex.c、mutex_w32.c、mutex_unix.c和mutex_noop.c这几个文件里,另外还有一个test_mutex.c文件做mutex的相关测试。
1.使用
这里以malloc.c里对互斥锁的使用先举个例子
sqlite3MutexInit();//在程序启动时要先初始化互斥锁接口
……
mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);//新建互斥锁,并分配空间,参数是锁的类型
……
sqlite3_mutex_enter(mem0.mutex);//进入临界区先加锁,如果已经有其他线程在使用该锁,那么线程会挂起,等待锁释放。
……//临界区
sqlite3_mutex_leave(mem0.mutex); //离开临界区时释放锁
2.初始化
将真实的mutex接口保存在sqlite3GlobalConfig.mutex,实际使用时只要调用保存接口的函数指针就可以了。
如果用户确定不使用多线程,还可以把锁接口配置成NoopMutex,该接口不存在互斥锁,使用于单线程的程序,速度会更快。
int sqlite3MutexInit(void){
int rc = SQLITE_OK;
if( !sqlite3GlobalConfig.mutex.xMutexAlloc ){
/* If the xMutexAlloc method has not been set, then the user did not
** install a mutex implementation via sqlite3_config() prior to
** sqlite3_initialize() being called. This block copies pointers to
** the default implementation into the sqlite3GlobalConfig structure.
*/
sqlite3_mutex_methods const *pFrom;
sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex;//保存互斥锁的使用接口
if( sqlite3GlobalConfig.bCoreMutex ){
pFrom = sqlite3DefaultMutex();//默认接口
}else{
pFrom = sqlite3NoopMutex();//不使用互斥锁,适用于单线程
}
pTo->xMutexInit = pFrom->xMutexInit;
pTo->xMutexEnd = pFrom->xMutexEnd;
pTo->xMutexFree = pFrom->xMutexFree;
pTo->xMutexEnter = pFrom->xMutexEnter;
pTo->xMutexTry = pFrom->xMutexTry;
pTo->xMutexLeave = pFrom->xMutexLeave;
pTo->xMutexHeld = pFrom->xMutexHeld;
pTo->xMutexNotheld = pFrom->xMutexNotheld;
sqlite3MemoryBarrier();//这是一个内存屏障,为了防止某些cpu指令的乱序执行
pTo->xMutexAlloc = pFrom->xMutexAlloc;
}
assert( sqlite3GlobalConfig.mutex.xMutexInit );
rc = sqlite3GlobalConfig.mutex.xMutexInit();
#ifdef SQLITE_DEBUG
GLOBAL(int, mutexIsInit) = 1;
#endif
return rc;
}
用户可以使用以下2个函数来获取mutex接口和更新接口,一般测试时会获取原来的接口,把原来的接口插入到自定的接口里做错误模拟,如
sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped.mutex);
sqlite3_config(SQLITE_CONFIG_MUTEX, &mutexmethods);
3.具体接口
win32下使用api接口有:
InitializeCriticalSection(&p->mutex);//初始化临界区
EnterCriticalSection(&p->mutex);//访问临界区,没有锁资源则挂起线程
LeaveCriticalSection(&p->mutex);//离开临界区
TryEnterCriticalSection(&p->mutex)//尝试访问临界区,没有锁资源则返回
DeleteCriticalSection(&p->mutex);//删除锁,释放p->mutex的空间
在linux下有类似的api接口pthread_mutex,这里就不一一介绍了。
锁类型为SQLITE_MUTEX_FAST和SQLITE_MUTEX_RECURSIVE的锁需要动态申请空间,其他类型的锁为静态分配的空间。
static sqlite3_mutex *winMutexAlloc(int iType){
sqlite3_mutex *p;
switch( iType ){
case SQLITE_MUTEX_FAST:
case SQLITE_MUTEX_RECURSIVE: {
p = sqlite3MallocZero( sizeof(*p) );
if( p ){
p->id = iType;
InitializeCriticalSection(&p->mutex);
}
break;
}
default: {
p = &winMutex_staticMutexes[iType-2];
p->id = iType;
break;
}
}
return p;
}
静态锁初始化时要调用winMutexInit()函数
static int winMutexInit(void){
/* The first to increment to 1 does actual initialization */
if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){
int i;
for(i=0; i<ArraySize(winMutex_staticMutexes); i++){
InitializeCriticalSection(&winMutex_staticMutexes[i].mutex);// 初始化静态锁
}
winMutex_isInit = 1;
}else{
/* Another thread is (in the process of) initializing the static
** mutexes */
while( !winMutex_isInit ){
sqlite3_win32_sleep(1);
}
}
return SQLITE_OK;
}
在访问临界区时会获取线程id并且增加引用计数,所以一般在前期测试时,在有些必须要加锁的地方可以对winMutexHeld()函数的返回值做断言,防止在访问临界区时忘加锁了。
static void winMutexEnter(sqlite3_mutex *p){
DWORD tid = GetCurrentThreadId();
……
EnterCriticalSection(&p->mutex);
assert( p->nRef>0 || p->owner==0 );
p->owner = tid;
p->nRef++;
……
}
static int winMutexHeld(sqlite3_mutex *p){
return p->nRef!=0 && p->owner==GetCurrentThreadId();
}