相信很多人都讀過《c++沉思錄》這本經典著作,在我艱難地讀完整本書後,留給我印象最深的只有一句話::「用類表示概念,用類解決問題」。
關於多執行緒程式設計,如果不是特別需要,大多數開發人員都不會特意去觸碰這個似乎神秘的領域。如果在某些場合能正確並靈活地運用,多執行緒帶來的好處是不言而喻的。然而,任何事物都有兩面性,如果程式中引入多執行緒,那麼我們需要謹慎小心地處理許多與之相關的問題,其中最突出的就是:資源競爭、死鎖和無限延遲。那麼物件導向與這些有什麼關係了嗎?有,物件導向的基礎是封裝,是的,正是封裝,可以很好的解決多執行緒環境中的主要困境。
一.多執行緒環境
在開始之前,有必要先重溫一下多執行緒環境。所謂,多執行緒程式設計,指的是在乙個程序中有多個執行緒同時執行,每乙個執行緒都有自己的堆疊,但是這些執行緒共享所有的全域性變數和資源。
在引入互斥量之前,多執行緒的主要問題是資源競爭,所謂資源競爭就是兩個或兩個以上的執行緒在同一時間訪問同一資源。為了解決這個問題,我們引入了互斥量,以同步多個執行緒對同一資源的訪問。
然而在引入互斥量之後,新的問題又來了。因為如果互斥量的獲得和釋放沒有得到正確處理,就會引起嚴重的問題。比如,某執行緒獲得互斥量後,就可以對受保護的資源進行訪問,但是如果訪問完畢後,忘記了釋放互斥量,那麼其它的執行緒永遠也無法訪問那個受保護的資源了。這是一種較簡單的情況,還有一種複雜的,那就是執行緒1已經擁有了資源a,但是它要擁有資源b後才能釋放a,而執行緒2了恰好相反,執行緒2已經擁有了資源b但是它要擁有資源a後才能釋放b。這樣一來,執行緒1和執行緒2就永遠地相互等待,這就是所謂的死鎖。
死鎖導致的問題是嚴重的,因為它使得程式無法正常執行下去。也許引入一些規範或約束有助於減少死鎖發生的機率,比如我們可以要求,所有資源的訪客(客戶,使用者)都必須在真正需要資源的時刻請求互斥量,而當資源一使用完畢,就立即釋放它,另外,鎖定與釋放一定要是成對的。如果上面的執行緒1和執行緒2都遵守這個規範,那麼上述的那種死鎖情況就不會發生了。
然而,規範永遠只是規範,規範能被執行多少要依賴於使用者的自覺程度有多高,這個世界上總是有對規範和約束視而不見的人存在。所以,我們希望能夠強制執行類似的約束,在對使用者透明的情況下。對於約束的強制實施可以通過封裝做到。 二
.多執行緒物件導向解決方案
首先你需要將系統api封裝成基礎類,這樣你就可以用物件導向的**類對付多執行緒環境,二是將臨界資源與對其的操作封裝在乙個類中。這兩點的核心都是將問題集中在乙個地方,防止它們氾濫在程式的各個地方。 1.
將系統api封裝成基礎類。 1.
將系統api封裝成基礎類。
來看看我封裝的幾個與多執行緒環境相關的基礎類。
// criticalsection
類用於解決對臨界資源的保護
class criticalsection
virtual ~criticalsection()
void lock()
void unlock()
}; //monitor
用於解決執行緒之間的同步依賴
class monitor
~monitor()
void setit()
void resetit()
void pulseit()
dword wait(long timeout)
}; //thread
是對執行緒的簡單封裝
class thread
~thread(void)
~thread(void)
} long getthethreadid()
void start(funptr pfn ,void* ppara)//
啟動執行緒
void setterminatesymbol(bool need_terminate)
void wait(void)
}; 在大多數的多執行緒環境中,上述的幾個類已經夠用了,不如要實現更強勁的同步機制,你可以仿照上面自己進行封裝。 2.
將臨界資源與對其的操作封裝在乙個類中,如果這樣做,鎖的操作自動在類的實現中完成,而外部使用者不用關心是否處在多執行緒環境。也就是說這個類是執行緒安全的,在單執行緒和多執行緒環境下都可以使用。
比如我們經常需要使用執行緒安全的容器,我就自己封裝了乙個:
// safeobjectlist
執行緒安全的容器
#include
#include "../threading/criticalsection.h"
plateclass safeobjectlist : criticalsection
void remove(t obj)
} this->unlock() ;
} void clear()
int count()
bool contains(t& target)
} this->unlock() ;
return found ; }
bool getelement(int index ,t& result)
i++ ;
}
succeed = true ; }
this->unlock() ;
return succeed ;
} };
在將臨界資源與對其的操作封裝在乙個類中的時候,我們特別要需要注意的一點是封裝的執行緒安全的方法(函式)的粒度,粒度太大則難以復用,粒度太小,則可能會導致鎖的巢狀。所以在封裝的時候,一定要根據你的具體應用,視情況而定。我的經驗是這樣的,首先可以把粒度定小一點,但是一旦發現有鎖的巢狀出現,就加大粒度,把這兩個巢狀合併成乙個稍微粗一點粒度的方法。
**:
C 多執行緒物件導向解決方案
相信很多人都讀過 c 沉思錄 這本經典著作,在我艱難地讀完整本書後,留給我印象最深的只有一句話 用類表示概念,用類解決問題 關於多執行緒程式設計,如果不是特別需要,大多數開發人員都不會特意去觸碰這個似乎神秘的領域。如果在某些場合能正確並靈活地運用,多執行緒帶來的好處是不言而喻的。然而,任何事物都有兩...
多執行緒解決方案及效能
程式操作的開銷 取自 深入理解並行程式設計 表3.1 操 作 開 銷 ns 比 率 單週期指令 0.61.0 最好情況的cas 37.9 63.2 最好情況的鎖 65.6 109.3 單次快取未命中 139.5 232.5 cas快取未命中 306.0 510.0 光纖通訊 3,000 5000 全...
C 多執行緒死鎖問題與解決方案
當乙個多執行緒程式中存在多個互斥資源時,就有可能造成死鎖。比如有兩個執行緒t1和t2,兩個互斥鎖a和b,執行緒t1拿到了鎖a,在等待鎖b,一直到等到b才能往下執行,釋放鎖a,而此時執行緒t2拿到了鎖b,在等待鎖a,一直到等到a才能往下執行,然後釋放鎖b。即執行緒t1和t2在等對方持有的鎖,又都不肯釋...