1、涉及到的源文件代码节选
1)storage/innobase/include/srw_lock.h
#ifndef _SRW_LOCK_H_
#define _SRW_LOCK_H_
#include <assert.h>
#include <atomic>
using namespace std;
#define DBUG_ASSERT assert
#define HOLDER (1U << 31)
/** Futex-based mutex */
class srw_mutex final
{
/** The lock word, containing HOLDER and a count of waiters */
atomic<uint32_t> lock;
/** Identifies that the lock is being held */
//static constexpr uint32_t HOLDER= 1U << 31;
/** Wait until the mutex has been acquired */
void wait_and_lock();
/** Wait for lock!=lk */
inline void wait(uint32_t lk);
/** Wake up one wait() thread */
void wake();
public:
/** @return whether the mutex is being held or waited for */
bool is_locked_or_waiting() const
{ return lock.load(memory_order_relaxed) != 0; }
/** @return whether the mutex is being held by any thread */
bool is_locked() const
{ return (lock.load(memory_order_relaxed) & HOLDER) != 0; }
void init() { DBUG_ASSERT(!is_locked_or_waiting()); }
void destroy() { DBUG_ASSERT(!is_locked_or_waiting()); }
/** @return whether the mutex was acquired */
bool wr_lock_try()
{
uint32_t lk= 0;
return lock.compare_exchange_strong(lk, HOLDER,
memory_order_acquire,
memory_order_relaxed);
}
void wr_lock() { if (!wr_lock_try()) wait_and_lock(); }
void wr_unlock()
{
const uint32_t lk= lock.fetch_and(~HOLDER, memory_order_release);
if (lk != HOLDER)
{
DBUG_ASSERT(lk & HOLDER);
wake();
}
}
};
#endif
2)storage/innobase/sync/srw_lock.cc
#include <sys/syscall.h>
#include <unistd.h>
#include "my_cpu.h"
#include "srw_lock.h"
#ifdef __linux__
# include <linux/futex.h>
# include <sys/syscall.h>
# define SRW_FUTEX(a,op,n) \
syscall(SYS_futex, a, FUTEX_ ## op ## _PRIVATE, n, nullptr, nullptr, 0)
#elif defined __OpenBSD__
# include <sys/time.h>
# include <sys/futex.h>
# define SRW_FUTEX(a,op,n) \
futex((volatile uint32_t*) a, FUTEX_ ## op, n, nullptr, nullptr)
#else
# error "no futex support"
#endif
inline void srw_mutex::wait(uint32_t lk) { SRW_FUTEX(&lock, WAIT, lk); }
void srw_mutex::wake() { SRW_FUTEX(&lock, WAKE, 1); }
void srw_mutex::wait_and_lock()
{
uint32_t lk= 1 + lock.fetch_add(1, std::memory_order_relaxed);
for (auto spin= srv_n_spin_wait_rounds; spin; spin--)
{
lk&= ~HOLDER;
DBUG_ASSERT(lk);
while (!lock.compare_exchange_weak(lk, HOLDER | (lk - 1),
std::memory_order_acquire,
std::memory_order_relaxed))
if (lk & HOLDER)
goto occupied;
return;
occupied:
ut_delay(srv_spin_wait_delay);
}
for (;;)
{
lk= lock.load(std::memory_order_relaxed);
while (!(lk & HOLDER))
{
DBUG_ASSERT(lk);
if (lock.compare_exchange_weak(lk, HOLDER | (lk - 1),
std::memory_order_acquire,
std::memory_order_relaxed))
return;
}
DBUG_ASSERT(lk > HOLDER);
wait(lk);
}
}
2、加解锁的逻辑
1)初始时 lock 的值为 0,如果当前锁没有被占用,则通过 exchange 方法(srw_lock.h 文件的第 41 行)将 lock 的值置为 1000...0000,即最高位为 1,表示当前锁已经被占用,解锁时,将 lock 的最高位置为 0(srw_lock.h 文件的第 49 行)。
2)如果当前锁已经被占用,则 exchange 方法(srw_lock.h 文件的第 41 行)返回为 false,此时将调用 wait_and_lock 函数(srw_lock.h 文件的第 46 行)进行加锁。
在该函数中,先将 lock 的值加 1 赋给 lk,同时 lock 自增 1 (srw_lock.cc 文件的第 25 行),也就是说,此时 lock 和 lk 的最高位都为 1。(如果接下来其他线程很快解锁了,那么 lock 的最高位将会为 0)。
接着, 抹去 lk 的最高位(srw_lock.cc 文件的第 28 行),然后判断 lock 和 lk 是否相等(srw_lock.cc 文件的第 30 行),由上面可知,只有在解锁的情况下,lock 和 lk 才可能相等(因为此时 lk 的最高位已经为 0 了)。
如果 lock 和 lk 相等,则 exchange 方法返回成功,即加锁成功,此时将 lock 的最高位置为 1(表示锁已被占用), 同时将 lock 的低位置为 lk - 1 (srw_lock.cc 文件的第 30 行),这是为了在解锁后能够让其他等待的线程加锁成功。
加锁成功后函数返回(srw_lock.cc 文件的第 35 行)。如果加锁失败,则将 lock 的值赋给 lk (exchang 方法自身的逻辑),即最高位为 1,同时第 33 行的判断条件为真,此时需要通过 ut_delay 函数进行延迟等待(srw_lock.cc 的第 37 行),然后继续尝试加锁。
3)如果连续尝试 srv_n_spin_wait_rounds 次加锁都未成功,那么线程将通过 wait 函数进行阻塞(srw_lock.cc 文件的第 52 行),唤醒的逻辑在解锁里面(srw_lock.h 的第 53 行),阻塞和唤醒都是通过系统调用 futex 实现的(srw_lock.cc 文件的第 10 行)。
唤醒后,继续尝试加锁 (srw_lock.cc 文件的第 46 行),如果还是失败,则再次阻塞、等待唤醒、尝试加锁,直到加锁成功。