dll的多進程多線程安全的幾種策略

 轉的]dll的多進程多線程安全的幾種策略

2008年07月26日 星期六 下午 04:32

[from:http://www.cnblogs.com/moonz-wu/archive/2008/05/08/1189021.html]

1、動態庫只有一個導出函數。

這種情況非常少,也是最容易處理的情況。這種情況下編寫函數時,只需要考慮不要有衝突的全局數據就可以了。這裏的全局數據包括了在堆中分配的數據塊和靜態全局變量等。如果存在這樣的全局數據,那麼進程中的不同線程訪問這個函數就會造成衝突。

解決辦法也很簡單,就是儘量用堆棧(stack)來解決問題。由於堆棧的所有人是線程,所以它必然是線程安全的。當然也要注意避免堆棧溢出。

我們都知道,如果要在函數再次調用時保留前一次調用的狀態,可以使用靜態變量。但如果你要保持函數的線程安全,那麼靜態變量是不能用的,因爲靜態變量是全局的,是屬於進程的,也就是屬於進程內線程共享的。所以如果確實需要在同一線程中保持函數的狀態,相當於在不同次調用間傳遞參數,可以考慮使用靜態全局線程局部變量,即:
__declspec( thread ) int tls_i = 1;
該變量定義就使編譯器保證了tls_i是對應於每個線程的,即每個線程都一個tls_i的副本(copy),這樣必然就是線程安全的。

2、動態庫導出了多個函數,而且多個函數間存在數據傳遞。

就像前面說的,一般DLL都導出多個函數,一個初始化,一個資源釋放,其他爲核心功能函數。這些函數間極有可能發生數據傳遞。如果一個初始化函數是在線程A中調用的,而核心功能函數是在線程B中調用的,那麼線程A初始化函數的資源就無法對應線程B中的核心功能,此外還有核心功能函數間的數據傳遞,這樣的DLL就不是線程安全的,必然導致錯誤。

解決辦法是由用戶(即使用DLL的人)保證這些導出函數是在一個線程中調用。但這樣會很大程度上限制接口的設計和用戶的使用自由度。所以最好的方法是函數只管自己的線程安全,不同函數傳遞數據用動態TLS,線程局部存儲。

比如:
我在全局定義了一個變量,用於存儲當前線程局部存儲的index ID。
__declspec( thread ) int tls_i = 1;
當調用分配資源的函數時,調用動態TLS函數TlsAlloc,分配一個ID,將其記錄在全局的線程安全的tls_i變量,並通過TlsSetValue函數將數據保存在線程安全的區域;當調用獲取資源的函數時,通過TlsGetValue獲取資源,處理完成後,調用Tlsfree對TLS index釋放,以便新線程佔有。

這樣,只要DLL中每個函數保證其局部是線程安全的,函數間傳遞數據通過TLS(靜態和動態),就可以實現整個DLL的線程安全。

3、限制訪問DLL中某一函數的線程數目。

有時候,對於DLL中的某一個函數的訪問線程數目是有限制的,超過了限制其他線程就得等一定的時間,一定的時間過後如果還不能得到執行機會,那就返回超時。這樣的設計對用戶來說是友好的,而且很實用,有的商業程序確實是按照允許用戶訪問的通道數目來計價的。

對DLL中的函數做這樣的一個封裝,一般是簡單的待用Semaphore信號量,來解決。DLL初始化時調用CreateSemaphore函數對信號量進行初始化,其原型如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
                       // pointer to security attributes
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName       // pointer to semaphore-object name
);
對於信號量,它每WaitForSingleObject一次(當然是要進入),其狀態值(一個整數)就減1,使用完ReleaseSemaphore其狀態值就加1,當其狀態值爲0時信號量就由有信號變爲無信號。利用信號量的這一特性,我們在初始化時將信號量的初始值(第2個參數)設置爲限制的線程訪問數目。在要限制訪問線程數目的函數內部,通過調用WaitForSingleOject獲取控制權,並指定一個等待時間(這個由配置文件指定),根據情況超時返回,使用完ReleaseSemaphore釋放對佔用,讓其他線程可以調用這個函數。

4、多進程情況下的多線程安全DLL。

前面3講了有時候需要對某一函數的訪問線程進行限制,而我們知道,DLL是可以被多個進行加載並調用的。那就是說如果我們只對一個進程進行了限制,那麼在多進程調用的情況下,這樣的限制被輕易攻破。

我們都知道,Semaphore信號量屬於內核對象,也就是說其可以被多進程共享訪問,也就說,如果我們給一個Semaphore指定了一個名字,在另一個進程中,我們只要調用OpenSemaphore函數用同一名字打開信號量就可以訪問了。這樣問題就解決了?

現實情況是,多進程情況下,一般不是簡單的多進程共享一個Semaphore就可以了。多進程間需要互通很多信息。一般的解決辦法是,採用共享數據段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS")
通過pragam編譯器指令生成了一個名叫share的共享數據段,這樣對於變量share_data就可以多進程共享的了。如果要多進程間交換數據,只要在data_seg中添加數據定義即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章