标签(空格分隔): innodb
Innodb中大量使用自旋锁来避免锁等待时的上下文切换,影响性能的问题。自旋锁分为加锁和解锁两个过程,其中加锁分为尝试加锁与自旋的过程。
其上层调用方式一般为如下:
mutex_enter(&trx->undo_mutex);
mutex_enter宏定义如下
#define mutex_enter(M) (M)->enter( \
srv_n_spin_wait_rounds, \
srv_spin_wait_delay, \
__FILE__, __LINE__)
所以根据不同的mutex类型,有不同的实现。有点类似与C++中的多态。
而如上示例中的undo_mutex定义如下
UndoMutex undo_mutex; /*!< mutex protecting the fields in this
section (down to undo_no_arr), EXCEPT
last_sql_stat_start, which can be
accessed only when we know that there
cannot be any activity in the undo
logs! */
UndoMutex的声明为
typedef ib_mutex_t UndoMutex;
ib_mutex_t的声明为
typedef SyncArrayMutex ib_mutex_t;
SyncArrayMutex的声明为
UT_MUTEX_TYPE(TTASEventMutex, GenericPolicy, SyncArrayMutex);
UT_MUTEX_TYPE的宏定义为
/** Create a typedef using the MutexType
@param[in] M Mutex type
@param[in[ P Policy type
@param[in] T The resulting typedef alias */
#define UT_MUTEX_TYPE(M, P, T) typedef PolicyMutex > T;
展开就是
typedef PolicyMutex > SyncArrayMutex;
其中PolicyMutex的模版定义为
template
struct PolicyMutex
{
}
TTASEventMutex的模版定义为
template class Policy = NoPolicy>
struct TTASEventMutex
{
}
GenericPolicy的模版定义为
template
struct GenericPolicy
好吧,我已经不想再展开了,痛苦。。
我们看下函数调用吧
如下,if (!try_lock()),则证明尝试加锁失败,进入自旋操作。
/** Acquire the mutex.
@param[in] max_spins max number of spins
@param[in] max_delay max delay per spin
@param[in] filename from where called
@param[in] line within filename */
void enter(
uint32_t max_spins, //MySQL的参数设置innodb_sync_spin_loops
uint32_t max_delay, //MySQL的参数设置innodb_spin_wait_delay
const char* filename,
uint32_t line)
UNIV_NOTHROW
{
if (!try_lock()) {
spin_and_try_lock(max_spins, max_delay, filename, line);
}
}
从代码上来看是先进行一次try_lock,下面来看
/** Try and lock the mutex. Note: POSIX returns 0 on success.
@return true on success */
bool try_lock()
UNIV_NOTHROW
{
return(tas_lock());
}
tas_lock()
/** Try and acquire the lock using TestAndSet.
@return true if lock succeeded */
bool tas_lock() UNIV_NOTHROW
{
return(TAS(&m_lock_word, MUTEX_STATE_LOCKED)
== MUTEX_STATE_UNLOCKED);
}
TAS宏定义
#define TAS(l, n) os_atomic_test_and_set((l), (n))
os_atomic_test_and_set
/** Do an atomic test and set.
@param[in,out] ptr Memory location to set
@param[in] new_val new value
@return old value of memory location. */
UNIV_INLINE
lock_word_t
os_atomic_test_and_set(
volatile lock_word_t* ptr,
lock_word_t new_val)
{
lock_word_t ret;
/* Silence a compiler warning about unused ptr. */
(void) ptr;
#if defined(__powerpc__) || defined(__aarch64__)
__atomic_exchange(ptr, &new_val, &ret, __ATOMIC_SEQ_CST);
#else
__atomic_exchange(ptr, &new_val, &ret, __ATOMIC_RELEASE);
#endif
return(ret);
}
最终调用是 __atomic_exchange(ptr, &new_val, &ret, __ATOMIC_RELEASE); __atomic_exchange
是gcc的内置原子函数,用来实现原子操作。
意思为拿new_val去替换ptr中的值,然后返回ptr之前存储的值。__ATOMIC_RELEASE
的意思是?
ok,我们返回到tas_lock(),如果返回值为0,则证明当前是没有其他线程占用锁的,则加锁成功,并且m_lock_word被设置为MUTEX_STATE_LOCKED,也就是1,下一次别的线程进行tas_lock,则会返回MUTEX_STATE_LOCKED。置于MUTEX_STATE_WAITERS,虽然知道其含义,但是暂时还没有发现其用途。
/** Try and acquire the lock using TestAndSet.
@return true if lock succeeded */
bool tas_lock() UNIV_NOTHROW
{
return(TAS(&m_lock_word, MUTEX_STATE_LOCKED)
== MUTEX_STATE_UNLOCKED);
}
---------
/** Mutex states. */
enum mutex_state_t {
/** Mutex is free */
MUTEX_STATE_UNLOCKED = 0,
/** Mutex is acquired by some thread. */
MUTEX_STATE_LOCKED = 1,
/** Mutex is contended and there are threads waiting on the lock. */
MUTEX_STATE_WAITERS = 2
};
尝试加锁部分结束,下面来看自旋操作,也就是spin_and_try_lock(max_spins, max_delay, filename, line)
部分;
函数实现和注释如下
/** Spin while trying to acquire the mutex
@param[in] max_spins max number of spins
@param[in] max_delay max delay per spin
@param[in] filename from where called
@param[in] line within filename */
void spin_and_try_lock(
uint32_t max_spins,
uint32_t max_delay,
const char* filename,
uint32_t line)
UNIV_NOTHROW
{
uint32_t n_spins = 0;//当前自旋次数
uint32_t n_waits = 0;//等待次数
const uint32_t step = max_spins;
os_rmb;//# define os_rmb __atomic_thread_fence(__ATOMIC_ACQUIRE)
for (;;) {
/* If the lock was free then try and acquire it. */
if (is_free(max_spins, max_delay, n_spins)) {
if (try_lock()) {
break;
} else {
continue;
}
} else {
max_spins = n_spins + step;
}
++n_waits;
os_thread_yield();
/* The 4 below is a heuristic that has existed for a
very long time now. It is unclear if changing this
value will make a difference.
NOTE: There is a delay that happens before the retry,
finding a free slot in the sync arary and the yield
above. Otherwise we could have simply done the extra
spin above. */
if (wait(filename, line, 4)) {
n_spins += 4;
break;
}
}
/* Waits and yields will be the same number in our
mutex design */
m_policy.add(n_spins, n_waits);
}
聚焦到for(;;)
中
判断is_free,函数实现如下
解释:
在一个while循环中判断is_locked()
,如果依然处于锁定状态,则执行ut_delay(ut_rnd_interval(0, max_delay))
,这个也是自旋的根本所在,然后自增自旋次数。
/** Spin and wait for the mutex to become free.
@param[in] max_spins max spins
@param[in] max_delay max delay per spin
@param[in,out] n_spins spin start index
@return true if unlocked */
bool is_free(
uint32_t max_spins,
uint32_t max_delay,
uint32_t& n_spins) const
UNIV_NOTHROW
{
ut_ad(n_spins <= max_spins);
/* Spin waiting for the lock word to become zero. Note
that we do not have to assume that the read access to
the lock word is atomic, as the actual locking is always
committed with atomic test-and-set. In reality, however,
all processors probably have an atomic read of a memory word. */
do {
if (!is_locked()) {
return(true);
}
ut_delay(ut_rnd_interval(0, max_delay));
++n_spins;
} while (n_spins < max_spins);
return(false);
}
下面来看下ut_delay
的实现
/*************************************************************//**
Runs an idle loop on CPU. The argument gives the desired delay
in microseconds on 100 MHz Pentium + Visual C++.
@return dummy value */
ulint
ut_delay(
/*=====*/
ulint delay) /*!< in: delay in microseconds on 100 MHz Pentium */
{
ulint i, j;
UT_LOW_PRIORITY_CPU();
j = 0;
for (i = 0; i < delay * 50; i++) {
j += i;
UT_RELAX_CPU();
}
UT_RESUME_PRIORITY_CPU();
return(j);
}