多線程程序中,我們經常會遇到這種情況,主線程啟動時加載一些參數到內存中的某個對象或者數據結構中,將這個對象或數據結構作為參數傳入各個子線程中,為了避免對象的復制與拷貝,往往傳入的是指針,子線程啟動,進行業務邏輯處理,需要根據key值獲取hashtable中的value,value = m_pParam->get(key),代碼如下所示
//用hashtable保存程序運行所需的參數
hashtable<key, value> hashParam;
void loadParam(hashtable<key, value> & hashParam)
{
//加載參數
return;
}
//模擬線程類
class ThreadX
{
public:
ThreadX(hashtable<key, value> *pParam):m_pParam(pParam)
{...}
T * get(int key)
{
return m_pParam->get(key);
}
/*其他成員*/
private:
hashtable<key, value> *m_pParam;
/*其他成員*/
};
主線程將構造好的hashtable傳入子線程,這裡的子線程對hashtable的操作是只讀操作,保證其他線程也不會對hashtable的數據進行修改,所以這裡的操作沒有加鎖。隨著需求的變更,現在hashtable中的數據需要支持動態刷新,即之前的參數可能會有變動(數據庫中的參數、配置文件信息等),在程序運行中通過發送信號量,由主線程對參數進行重新加載。通常的做法是,對m_hashParam+互斥鎖進行封裝,設結構應為SynParam,在操作時上鎖:主線程加載參數,lock() -> 讀取參數到內存 -> m_hashParam->put(key, value) -> unlock(),重新加載參數時,同樣的方式處理。子線程同樣保存封裝後的結構體SynParam的指針,m_pSynParam,並在操作時,進行加鎖保護。這樣在程序收到參數刷新的信號量時,主線程對參數進行刷新,而子線程讀取到的是最新的數據。注意在子線程中 m_SynParam->get(key)時,同樣需要加鎖,這樣才能保證讀取到底數據是正確的,原理等同於一個線程寫多個線程讀的經典問題。在程序中參數刷新的頻率遠低於子線程中對參數結構的讀取,雖然能夠保證參數每次都讀取到最新的,但是加鎖的代價實在太高,會影響到程序的效率。
現在考慮另一種方式,主線程依然加載數據到內存中,假設加載到hashParam_A中,設 phashParam = &hashParam_A,主線程中創建子線程,將phashParam作為參數傳遞給子線程,注意,子線程中保存phashParam的地址,m_pphash = &phashParam,即pphash = &(&m_hashParam),在子線程中,獲取key值操作表示如: value = (*pphash)->get(key); 這裡同樣只讀操作,並沒有加鎖:
void loadParam(hashtable<key, value> & hashParam)
{
//加載參數
return;
}
class ThreadX
{
public:
ThreadX(hashtable<key, value> *pParam):m_pParam(&pParam)
{...}
/*其他成員*/
T * get(int key)
{
value = (*m_ppParam)->get(key);
}
private:
hashtable<key, value> **m_ppParam; //注意這裡是二級指針
/*其他成員*/
};
hashtable<key, value> hashParam_A;
loadParam(hashParam_A);
hashtable<key, value> * pParam = &hashParam_A;
ThreadX thd(pParam);
在參數刷新時,主線程加載內存到另一個同樣的結構體 m_hashParamB中,這時令 pParam = &hashParamB,pParam的值改變了,而子線程中的m_ppParam指向phash,所以*pphash的值也改變了,實際指向hashParamB,即新的內存結構:
hashtable<key, value> hashParam_B;
loadParam(hashParam_B);
hashtable<key, value> * pParam = &hashParam_B;
注意phash=&hashParam_B沒有加鎖的原因是因為在32位的平台,這裡是一個原子操作,所以可以保證子線程value = (*m_ppParam)->get(key); 時得到最新的參數值。如果是64位的平台,對pParam = &hashParam_B的指針賦值操作,可能會被分解成2條指令,可能會導致在某個子線程獲取value,對*m_ppParam解引用時,*ppParam指向的表示一個錯誤的地址!