1. 程式人生 > 資料庫 >MySQL Server層的原子操作

MySQL Server層的原子操作

看如下程式碼

#test.cc

int i=0;

void* funcA(void* arg)
{
    for(int i=0;i<10;i++)
    {
        ++i;
    }
    
}

void* funcB(void* arg)
{
    for(inti=0;i<10;i--)
    {
        --i;
    }
}

int main()
{
    pthread_create(..funcAdd..);
    pthread_create(..funcAdd..);
    cout << i << endl;
    return 0;
}

多執行緒併發更新全域性變數i的值,最終的結果是不確定的。

要想得到預期結果0,需要在對變數i進行遞增或者遞減操作時,加鎖處理。
程式碼如下:

#test.cc

pthread_mutex_t m_i;
int i=0;

void* funcA(void* arg)
{
    for(int i=0;i<10;i++)
    {
        lock(&m_i);
        ++i;
        unlock(&m_i);
    }
    
}

void* funcB(void* arg)
{
    for(inti=0;i<10;i--)
    {
        lock(&m_i);
        --i;
        unlock(&m_i);
    }
}

int main()
{
    pthread_create(..funcAdd..);
    pthread_create(..funcAdd..);
    cout << i << endl;
}

但是加鎖等待,被喚醒,解鎖這一系列操作,意味著需要進行頻繁的上下文切換,影響效能。為了避免這個問題,gcc4.1.2版本之後,提供了一系列的原子操作,也就說,不需要引入鎖的保護,來實現對變數的併發操作。

MySQL對這些操作進行了封裝,InnoDB內部spin lock的實現也是利用了原子操作,server層也提供了同樣的原子操作。

來看一個MySQL server層的示例:Global_THD_manager中表示當前處於running狀態的執行緒數的成員變數,如下:

  volatile int32 num_thread_running;

每當有一個執行緒從sleep狀態轉為執行狀態時,都需要讓num_thread_running遞增,而結束執行時,需要遞減,這就和本文開始的場景完全一樣。如果通過鎖來保護num_thread_running變數,在高併發下必定會影響效能。

負責遞增和遞減num_thread_running變數的兩個函式

  void inc_thread_running()
  {
    my_atomic_add32(&num_thread_running,1);
  }

  /**
    Decrements thread running statistic variable.
  */
  void dec_thread_running()
  {
    my_atomic_add32(&num_thread_running,-1);
  }

其中my_atomic_add32的實現如下:

static inline int32 my_atomic_add32(int32 volatile *a,int32 v)
{
  return __sync_fetch_and_add(a,v);
}

它通過__sync_fetch_and_add函式來實現對變數a的add操作。

下圖是MySQL提供的其他原子操作集合

別的模組不直接使用過__sync_fetch_and_add等一系列的函式,而是使用封裝過後的以my*打頭的函式。