2012-10-02 18 views
0

我正在尋找一些關於OpenMP的建議。我對使用threadprivate變量感到困惑。這個問題可以在下面的例子中呈現:在動態DLL中使用線程私有變量?

----------- 

// The code below is located the dynamically loaded DLL. 
/* Global variable. */ 
int *p; 
#pragma omp threadprivate(p) 

extern "C" __declspec(dllexport) int MyFunc1(void) 
{ 
    int i; 
    #pragma omp parallel for 
    for (i = 0; i < n; i++) { 
    MyFunc2(i); 
    } 
    return TRUE; 
} 

void MyFunc2(void) 
{ 
    p = malloc(sizeof(int)); 
    *p = 0; 
    printf(「value = %d」,*p); 
    free(p); 
} 

----------- 

在這裏,我想每個線程都有一個全局線程無關的變量,將在該線程的所有功能可見的單獨副本。該變量將在線程中初始化並銷燬。

「問題」在這裏是包括全局變量「p」的定義中的所有代碼位於動態加載DLL(通過調用LoadLibrary)。

微軟說http://msdn.microsoft.com/en-us/library/2z1788dd.aspx「你不能在任何將通過LoadLibrary加載的DLL中使用threadprivate。這包括使用/ DELAYLOAD(延遲加載導入)加載的DLL,它也使用LoadLibrary。「因此,如果我弄明白了,上面的代碼是不正確的 - threadprivate變量和動態加載的DLL不會混合。

爲了驗證這一點,我創建動態加載DLL加載運行使用線程專用如上所述並聯的功能的測試項目。這一切都很好!

嗯......現在我很困惑,因爲該項目不應該工作。

我真的可以在動態DLL中使用線程私有變量嗎?或者有一個技巧嗎?

謝謝

亞歷

+0

它沒有工作就好了。你預計它會失敗,並沒有失敗。不要做你期望的事情,永遠不要描述爲工作得很好。你打破了規則,並沒有達到你的預期。 *這是你首先應該預料到的。 –

回答

1

threadprivate MS中的OpenMP實現被轉換爲__declspec(thread),它將聲明的變量放在靜態TLS(線程本地存儲)中。當程序啓動時,TLS的大小通過考慮可執行文件所需的TLS大小以及所有其他隱式加載的DLL的TLS要求來確定。當您使用LoadLibrary動態加載另一個DLL或使用FreeLibrary將其卸載時,系統必須檢查所有正在運行的線程並相應地放大或縮小它們的TLS存儲。根據KB118816

這個過程是太多的操作系統進行管理,這可能會導致無論是當DLL被動態加載或代碼引用的數據的異常。

訪問這些變量被認爲是未定義的行爲。它適用於你的情況,但這並不意味着它可以在任何地方和任何地方工作。 Here你可以閱讀爲什麼它最有可能在Windows XP/2003和更早版本的Windows上失敗。根據同一來源,隱式TLS處理已在Windows Vista中重寫,因此OpenMP threadprivate__declspec(thread)應在運行時加載的DLL中正常運行。建議的解決方案是使用TlsAlloc

DWORD dwTlsIdx; 

extern "C" __declspec(dllexport) int MyFunc1(void) 
{ 
    int i; 
    #pragma omp parallel for 
    for (i = 0; i < n; i++) { 
     MyFunc2(i); 
    } 
    return TRUE; 
} 

void MyFunc2(void) 
{ 
    int **pp = (int **)TlsGetValue(dwTlsIdx); 
    *pp = malloc(sizeof(int)); 
    **pp = 0; 
    printf(「value = %d」,**pp); 
    free(*pp); 
} 

dwTlsIdx應該在過程中初始化附加在DllMain一起TlsAlloc通話。應該在線程附加上分配足夠的內存以保存int *,並且應將其地址設置爲dwTlsIdx TLS索引的值。或者你可以做到這一點在第一次調用MyFunc2代替:

void MyFunc2(void) 
{ 
    int **pp = (int **)TlsGetValue(dwTlsIdx); 
    if (pp == NULL) 
    { 
     pp = malloc(sizeof(int *)); 
     TlsSetValue(dwTlsIdx, pp); 
    } 
    *pp = malloc(sizeof(int)); 
    **pp = 0; 
    printf(「value = %d」,**pp); 
    free(*pp); 
} 

詳情請參閱here(錯誤檢查)。

+0

我在上述文章中沒有看到與「如果沒有足夠的資源,功能可能會失敗」不同的東西。他們沒有指出任何具體問題。他們沒有提及任何最近的Windows版本。答案中的代碼示例仍將數據存儲在同一堆中。你對'malloc'的調用很可能會以完全相同的方式失敗。 –

0

我不是用OMP的實驗,但我有很好的經驗與

DWORD WINAPI TlsAlloc(void); 

TlsXxx功能工作。我很肯定threadprivate(p)是最終實現此功能的包裝。此功能完全在exe文件的,靜態和動態加載的dll等

在一種情況下,我在我的過程中,同時看到約800 TLS索引。每個線程(大約200個線程)在其線程本地存儲中擁有該數量的對象。 NT正在堆中分配緩衝區來緩存這些數據。所有這一切都很好。

它可以是在當MSDN文章寫的時候,有一些問題,但最有可能的,現在是固定的。

我的2分。

+0

顯然它在Windows XP上仍然存在問題 - 請參閱[此票證](http://trac.wxwidgets.org/ticket/13116)。 –

0

Hristo和Kirill,

謝謝你的迴應。

看來,微軟已經解決了動態DLL中的TLS問題(至少在我正在使用的Windows 7中)。也許他們只是沒有更新文檔。我保持手指交叉。

我創建了一個測試項目,我通過__declspec(thread)pragmas(如Hristo Iliev在其他文章中向我推薦的)使用TLS測試了這個想法。該項目使用了大量的TLS變量(僅用於測試),並且一切正常。然後我移動了工作項目中的代碼。這是一個大型項目(大約100萬行代碼),它加載了許多動態DLL。它也在那裏工作。

我仍然堅持使用TlsAlloc,TlsSetValue,TlsGetValue函數的最壞情況的想法。但它並不適合我,因爲我擔心這些函數會對我所擁有的數字處理代碼增加一個重大的監聽。我的庫只需要幾個TLS變量,但是這些變量通過代碼廣泛使用,包括深層次的低級函數。

乾杯,

亞歷Yamchikov

+0

您可以獲得指向並行區域開始處的TLS區域的指針,並將其傳遞給調用堆棧以使用使用線程專用變量的函數。隱式TLS訪問也有價格 - 在64位模式下執行4個「MOV」指令,實現了「TlsGetValue(_tls_index)」的簡化版內聯版本。 –