文章編號: 163449 - 上次校閱: 2007年2月21日 - 版次: 4.2

在延伸預存程序中執行緒區域儲存區的使用

系統提示本文適用於您使用的作業系統之外的作業系統。與您不相關的文章內容已停用。

在此頁中

全部展開 | 全部摺疊

結論

執行緒本機存放區 (TLS) 可能會非常棘手的主旨。當使用執行緒,共用環境,是非常重要您了解 TLS 和執行緒互動的衍生結果。

強烈建議您避免在執行緒共用如 Microsoft SQL Server 的環境中的 TLS。不過,如果您必須使用 TLS,請仔細閱讀此文件並查閱在 Win32 SDK 線上文件中的 TLS 和 DLL 的主要功能。

而且,編譯器指示詞 __declspec(thread) 不支援延伸預存程序中。當 LoadLibarary 發生 __declspec(thread) 定義不正確初始化。如需詳細資訊,請參閱 < 進階 Windows"由 Jeffrey Ricter。



注意: 任何副檔名到 SQL Server 包括擴充的預存程序可能會永遠不會使 DisableThreadLibraryCalls() 的呼叫。

其他相關資訊

如記載,則 TlsAlloc 函數會傳回索引值,用於中 TlsSetValue 和 TlsGetValue 的函式呼叫。文件會建議您在 DLLMain 函式中呼叫 TlsAlloc 函式時 ul_reason_for_call = DLL_PROCESS_ATTACH。預設情況下,一開始載入 DLL 時,會呼叫 DLL_PROCESS_ATTACH。

在 DLL_PROCESS_ATTACH 和 DLL_THREAD_ATTACH,應該配置記憶體,並呼叫 TlsSetValue。這特別是執行緒共用開始之處案例中會造成一些問題。

下面是會套用至任何應用程式不會執行緒集區的範例案例。這個範例使用 Microsoft SQL Server 應用程式。

以下是您必須了解此範例的幾個基本概念。 這些會全部詳細記錄在 Win32 SDK 文件下 DLLMain 函式中。
  • 載入一個 DLL 時,它呼叫 DLL_PROCESS_ATTACH。
  • 所有後續的執行緒 (繁衍 DLL 載入後) 呼叫 DLL_THREAD_ATTACH。
  • 目前正在執行的執行緒 (繁衍載入 DLL 之前),請不要呼叫 DLL_THREAD_ATTACH。
  • 所有的執行緒呼叫 DLL_THREAD_DETACH,即使它們永遠都不會呼叫 DLL_THREAD_ATTACH。
比方說假設下列圖表顯示順序和背景工作執行緒集區的 SQL Server 的使用方式:
   Thread     ATTACH CALLED     COMMAND     USER
------------------------------------------------ 
     1              NO           select      Joe
     2             YES          xp_test     Mary
     3             YES           select     Adam

     1              NO          xp_test     Lynn
				

Joe 啟動長時間執行 Select 陳述式。沒有人有使用 xp_test 還,延伸預存程序,所以沒有 DLL_ATTACH 程序被呼叫的方法。

執行 Joe 的選取時 Mary 執行 xp_test。執行緒共用機制決定新的執行緒應該會繁衍服務出陶怡青的要求。SQL Server 接著會呼叫載入 Xproc.dll 檔案 LoadLibrary 函式。如此一來,執行緒 2 是第一個執行緒,因此稱為 「 DLL_PROCESS_ATTACH 附加至該 DLL。如稍早所述,TlsAlloc 可被呼叫來初始化延伸預存程序 TLS 索引值。

在執行 Joe 的] 和 [出陶怡青的命令時 Adam 送出他自己選取。 再次,新的執行緒被繁衍處理 Adam 的要求。因為之後發生了 [LoadLibrary 繁衍執行緒 3,3 的執行緒呼叫 DLL_THREAD_ATTACH。如記載,這是就像在此執行緒 3 的配置的記憶體和呼叫 TlsSetValue。

現在,假設 Joe 的選取已完成,所以 1 的工作者執行緒都有空來使用。崔送出 xp_test] 命令,然後指派給執行緒 1。執行緒 1 永遠不會呼叫 DLL_PROCESS_ATTACH 或 DLL_THREAD_ATTACH,因為之前已繁衍執行緒所呼叫之 LoadLibrary。

從這個範例中,您可以看到由執行緒 1 任何嘗試存取與 TlsGetValue TLS 記憶體會導致 NULL 指標所傳回。如果延伸預存程序不正確地撰寫檢查有這種情況,就會發生存取違規 (AV),當您嘗試寫入 NULL 地址。

下面是需要提及關於 TLS 和執行緒集區的幾個點。

如果您在共用的環境中使用 TLS,您必須永遠檢查 TlsGetValue NULL 傳回值。當您取得 NULL 時,您必須正確配置記憶體和呼叫 TlsSetValue 處理那些執行緒,其中繁衍之前 [LoadLibrary 中發生了。

另一個文件中未直接解決的警告是您將 [TLS 為配置記憶體 DLLMain 函式中之後 [LoadLibarary 繁衍每個執行緒即使執行緒永遠不會使用該函式在 DLL 中。以上範例會顯示這與執行緒 3。只執行過一選取,但它呼叫 TLS 記憶體配置給延伸預存程序的 DLL_THREAD_ATTACH。

如果您在執行緒,共用環境中放置這項設計,可能會配置可能永遠不會使用的記憶體。

以下是記憶體的最佳化配置最好的方法:
  1. 在 DLL_PROCESS_ATTACH,請從 TlsAlloc 取得正確的 TLS 索引值。
  2. 配置記憶體 DLL_PROCESS_ATTACH 或 DLL_THREAD_ATTACH 中。
  3. 設計用來取得 TLS 值的泛型函式 ; 如果是 NULL,正確地配置它,並呼叫 TlsSetValue。

    這個步驟會配置的記憶體限制實際使用那些執行緒。這可能會大幅降低背景工作執行緒的額外負荷和啟動時間。它也會建置中正確地處理 [LoadLibrary 發生之前已繁衍這些執行緒冗餘。
  4. 因為永遠呼叫 DLL_PROCESS_DETACH 和 DLL_THREAD_DETACH,釋放這些處理序的記憶體。
只有其他的可能仍不清楚是對 TlsAlloc 呼叫。 讀取文件,它可能會出現傳回 [TLS 索引值是全域的值。全域值但 TlaAlloc 的每一個呼叫傳回不同的索引值。這可讓兩個延伸預存程序,要求他們自己的 TLS 索引值,正確地處理自己的 TLS 資料而不會影響其他。

以下是延伸預存程序,可顯示在範例中所描述的行為:
   // 
   //    1. Start SQL Server from the command prompt to see the output:
   //       ...\Mssql\Binn\Sqlservr -c
   // 
   //    2. Run Xproctst.cmd to show the behavior.
   // 
   #include "windows.h"
   #include "stdio.h"
   #include "srv.h"
 
   #define           TLS_FAIL    0xFFFFFFFF

   DWORD          dwTlsIndex     =  TLS_FAIL;
   DWORD          dwCounter      =  0;
   CRITICAL_SECTION  csSync;

   // 
   //    Cleanup TLS memory
   // 
   void vCleanUpTls(void)
   {
      char *   strData     =  NULL;
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = TlsGetValue(dwTlsIndex);
         if(NULL != strData)
         {
            free(strData);
            printf("\n >>> Tls memory released by thread %ld",
   GetCurrentThreadId());
         }
      }
   }
   // 
   //    Setup the TLS pointer
   // 
   void vSetUpTls(void)
   {
      char *      strData  =  NULL;
      if(TLS_FAIL == dwTlsIndex)
      {
         dwTlsIndex = TlsAlloc();
      }
      // 
      //    Are we ready to go
      // 
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = (char *) calloc(256,1);
         if(strData)
         {
            printf("\n >>> Tls memory allocated by thread %ld",
   GetCurrentThreadId());
            if(TRUE == TlsSetValue(dwTlsIndex, strData))
            {
               // 
               //    Protect the counter.
               // 
               EnterCriticalSection(&csSync);
               sprintf(strData, "Counter = %ld", ++dwCounter);
               LeaveCriticalSection(&csSync);
            }
            else
            {
               printf("\n >>> *** Serious error *** TlsSetValue
               failed.\n");
            }
         }
         else
         {
            printf("\n >>> *** Serious error *** can not allocate
            memory.\n");
         }
      }
      else
      {
         printf("\n >>> *** Serious error *** TlsAlloc failed.\n");
      }
   }
   // 
   //    DLLMain
   // 
   BOOL APIENTRY DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID
   lpReserved)
   {
      char  strInfo[256]   =  "";
      switch(ul_reason_for_call)
      {
         case DLL_PROCESS_ATTACH:
            InitializeCriticalSection(&csSync);
            vSetUpTls();
            sprintf(strInfo, "\n >>> DLL_PROCESS_ATTACH Thread: %ld",
   GetCurrentThreadId());
            break;
        case DLL_THREAD_ATTACH:
            vSetUpTls();
            sprintf(strInfo, "\n >>> DLL_THREAD_ATTACH Thread: %ld",
   GetCurrentThreadId());
            break;
         case DLL_PROCESS_DETACH:
            vCleanUpTls();
            if(TLS_FAIL != dwTlsIndex)
               TlsFree(dwTlsIndex);
            DeleteCriticalSection(&csSync);
            sprintf(strInfo, "\n >>> DLL_PROCESS_DETACH Thread: %ld",
   GetCurrentThreadId());
            break;
         case DLL_THREAD_DETACH:
            vCleanUpTls();
            sprintf(strInfo, "\n >>> DLL_THREAD_DETACH Thread: %ld",
   GetCurrentThreadId());
            break;
      }
      printf(strInfo);
      return TRUE;
   }
   // 
   //    DLL function in the extended stored procedure to show TLS trap
   // 
   __declspec(dllexport) SRVRETCODE xp_Tls(SRV_PROC *pSrvProc)
   {
      char  strInfo[256]   =  "";
      char *   strData        =  NULL;
      sprintf(strInfo, "\n >>> Invoking xp_Tls on Thread: %ld",
   GetCurrentThreadId());
      printf(strInfo);
      if(TLS_FAIL != dwTlsIndex)
      {
         strData = TlsGetValue(dwTlsIndex);
         if(NULL != strData)
         {
            sprintf(strInfo, "\n >>> %s", strData);
            printf(strInfo);
         }
         else
         {
            printf("\n >>> *** Serious error *** TlsGetValue returned NULL,
   thread pooling not handled correctly.\n");
         }
      }
      return 1;
   }
				

其他相關資訊

SQL Server 7.0 和 SQL Server 2000 光纖

我們強烈防止使用 TLS 而我們不支援在光纖模式下使用 TLS。 在光纖模式中實際的執行緒可以能進行任何 TLS 不安全的許多原因變更。

這篇文章中的資訊適用於:
  • Microsoft SQL Server 2000 Enterprise Edition
  • Microsoft SQL Server 7.0 Standard Edition
  • Microsoft SQL Server 6.0 Standard Edition
  • Microsoft SQL Server 6.5 Standard Edition
關鍵字:?
kbmt kbhowto kbother kbprogramming kbusage KB163449 KbMtzh
機器翻譯機器翻譯
重要:本文是以 Microsoft 機器翻譯軟體翻譯而成,而非使用人工翻譯而成。Microsoft 同時提供使用者人工翻譯及機器翻譯兩個版本的文章,讓使用者可以依其使用語言使用知識庫中的所有文章。但是,機器翻譯的文章可能不盡完美。這些文章中也可能出現拼字、語意或文法上的錯誤,就像外國人在使用本國語言時可能發生的錯誤。Microsoft 不為內容的翻譯錯誤或客戶對該內容的使用所產生的任何錯誤或損害負責。Microsoft也同時將不斷地就機器翻譯軟體進行更新。
按一下這裡查看此文章的英文版本:163449? (http://support.microsoft.com/kb/163449/en-us/ )
Microsoft及(或)其供應商不就任何在本伺服器上發表的文字資料及其相關圖表資訊的恰當性作任何承諾。所有文字資料及其相關圖表均以「現狀」供應,不負任何擔保責任。Microsoft及(或)其供應商謹此聲明,不負任何對與此資訊有關之擔保責任,包括關於適售性、適用於某一特定用途、權利或不侵權的明示或默示擔保責任。Microsoft及(或)其供應商無論如何不對因或與使用本伺服器上資訊或與資訊的實行有關而引起的契約、過失或其他侵權行為之訴訟中的特別的、間接的、衍生性的損害或任何因使用而喪失所導致的之損害、資料或利潤負任何責任。