SQLite3源码学习(18) 互斥锁

互斥锁是为了保证在多线程时一些不可重入函数执行的串行化,有些函数如malloc等会操作一些共享数据,如果被重入了就会导致共享资源被破坏,从而出现逻辑错误,所以如果有多个线程对共享资源的访问就要加互斥锁。

互斥锁部分的代码还是比较简单的,代码实现在mutex.cmutex_w32.cmutex_unix.cmutex_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_FASTSQLITE_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();
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值