PostgreSQL V9.6 LWLock实现分析(四)

时间:2020-12-02 05:56:27

LWLock*的本质

   LWLock的锁操作,符合锁操作的技术本质,加锁同样是设置一个特定的标志位,释放锁是取消标识位。如下以CheckPoint相关操作为例,进行说明,其他类似。

   PostgreSQL定义LWLock结构体如下:

typedef struct LWLock  //LWLock的数据结构,绑定了锁、锁的属主、锁的等待者这三者,PostgreSQL 9.4.x之前的版本的实现方式有较大不同

{

   uint16        tranche;        /* tranche ID */  //LWLock上多了事务号,表明实施加锁操作的属主

    pg_atomic_uint32    state;    /* state of exclusive/nonexclusive lockers*/  //锁对象,一个标志对象,有多个标志位(通常是无符号32位数),即有32个标志位,不同标志位表达不同含义。之所以这样定义,是因为这样的锁需要同时表达多种含义(如设置为LW_FLAG_HAS_WAITERS同时还可设置为)

   dlist_head    waiters;        /* list of waiting PGPROCs */  //本锁的等待队列,表明那些进程在等待本锁释放

#ifdef LOCK_DEBUG

   pg_atomic_uint32 nwaiters;    /*number of waiters */ //等待者的数量

    structPGPROC *owner;         /* last exclusive owner of the lock */  //排它锁的属主

#endif

} LWLock;

    PostgreSQL定义了不同的标识符号,用以为“pg_atomic_uint32    state”标识不同的含义,已经在PostgreSQL中定义好的标识符号如下:

#define LW_FLAG_HAS_WAITERS    ((uint32) 1 << 30) //标志,LWLock锁有等待者

#define LW_FLAG_RELEASE_OK     ((uint32) 1 << 29) //标志,LWLock锁空闲,即没有施加锁(state值为“536870912”)

#define LW_FLAG_LOCKED         ((uint32) 1 << 28) //标志,LWLock锁存在,即已被加锁(state值为“268435456”)

 

#define LW_VAL_EXCLUSIVE       ((uint32) 1 << 24)  //锁的模式,LWLock排它锁模式,加锁的标志位之一,供加锁操作时使用

#define LW_VAL_SHARED          1                   //锁的模式,LWLock共享锁模式,加锁的标志位之一,供加锁操作时使用

#define LW_LOCK_MASK           ((uint32) ((1 << 25)-1)) //标志,LWLock锁标志

/* Must be greater than MAX_BACKENDS - which is2^23-1, so we're fine. */

#define LW_SHARED_MASK         ((uint32) ((1 << 24)-1))  //标志,LWLock存在共享锁

    PostgreSQL定义LWLock结构体之后,一个LWLock对象就可以出现在其他需要被保护的对象中,如进程对象(PGPROC)。

struct PGPROC  //进程的结构体,PostgreSQL是多进程结构

{...

    /*Per-backend LWLock.  Protects fieldsbelow (but not group fields). */

    LWLock       backendLock;  //定义LWLock锁,用于保护进程结构体中的共享资源不受并发操作破坏

    //如下对象被backendLock保护

    /* Lockmanager data, recording fast-path locks taken by this backend. */ 

   uint64        fpLockBits;        /* lock modes held for each fast-pathslot */

    Oid           fpRelId[FP_LOCK_SLOTS_PER_BACKEND];        /* slots for rel oids */

    bool          fpVXIDLock;        /* are we holding a fast-path VXIDlock? */

   LocalTransactionId fpLocalTransactionId;    /* lxid for fast-path VXID lock */

...};

    进程对象(PGPROC)通过InitProcGlobal()函数完成初始化,然后由此函数调用LWLockInitialize()函数完成对进程锁“backendLock”的初始化工作,即设置锁标志为“LW_FLAG_RELEASE_OK”。

void

InitProcGlobal(void)

{...

    for (i =0; i < TotalProcs; i++)

    {

      /*

         *Set up per-PGPROC semaphore, latch, and backendLock. Prepared xact

         * dummy PGPROCs don't need these though- they're never associated

         *with a real process

         */

        if (i< MaxBackends + NUM_AUXILIARY_PROCS)

        {

           PGSemaphoreCreate(&(procs[i].sem));

           InitSharedLatch(&(procs[i].procLatch));

            LWLockInitialize(&(procs[i].backendLock), LWTRANCHE_PROC);  //调用LWLockInitialize()初始化backendLock

        }

...

    }

...

}

    LWLockInitialize()函数要给“state”成员设置标识位为“LW_FLAG_RELEASE_OK”。

void

LWLockInitialize(LWLock *lock, int tranche_id)

{

   pg_atomic_init_u32(&lock->state,LW_FLAG_RELEASE_OK);  //设置无锁标志

#ifdef LOCK_DEBUG

   pg_atomic_init_u32(&lock->nwaiters, 0);

#endif

   lock->tranche = tranche_id;

   dlist_init(&lock->waiters);

}

    从以上的分析可以看出, PGPROC 结构体的初始化,是通过 LWLockInitialize() 函数在共享内存中对“ backendLock ”等完成,而“ backendLock ”的“ state ”是位于内存中的一块区域(无符号 32 bit 位的一段内存空间),这块区域用于标识是否用以保护 PGPROC 结构体。也就是说, LWLock 锁是内存中的一块区域,被用于置位,只是与 SpinLock 不同的是, LWLock 锁这块内存区域在不同的 bit 位设置不同的值以表示多种含义。另外 , 每个 LWLock 中有明确的属主和等待者,这起到了连接已加锁和申请加锁两种角色的作用,便于知晓谁在什么锁上等待谁这样的关系。