Common Intermediate Language и системное программирование в Microsoft.Net. Макаров А.В - 121 стр.

UptoLike

вот те данные, указатели на которые хранились в TLS памяти, –
нет. Необходимо специально отслеживать все возможные слу-
чаи завершения потоков, включая завершение по ошибке, и
принимать меры для освобождения выделенной памяти. При
использовании _declspec(thread) эта ситуация встречается ре-
же, так как позволяет хранить в _TLS сегментах данные любого
фиксированного размера.
Следует отметить еще один нюанс, связанный с использованием TLS
памяти, волокон и оптимизации. В частных случаях волокна могут испол-
няться разными потоками – при этом одно и то же волокно должно иметь
доступ к TLS памяти именно того потока, в котором оно в данный момент
исполняется. А если компилятор генерирует оптимизированный код, то
он может разместить указатель на данные TLS памяти в каком-либо реги-
стре или временной переменной, что при переключении волокна на дру-
гой поток приведет к ошибке – будет использована TLS память предыду-
щего потока. Чтобы избежать такой ситуации, компилятору можно ука-
зать специальный ключ /GT, отключающий некоторые виды оптимизации
при работе с TLS памятью. Это может потребоваться в крайне редких слу-
чаях – когда приложение использует несколько волокон, исполняемых в
нескольких потоках, и при этом волокна должны использовать TLS па-
мять потоков.
Аналогично TLS памяти, Windows поддерживает память, локальную
для волокон, – так называемую FLS память, или Fiber Local Storage. При
этом FLS память не зависит от того, какой именно поток выполняет дан-
ную нить. Для работы с FLS памятью Windows предоставляет набор функ-
ций, аналогичный Tls-функциям, отличие заключается только в функции
выделения ячейки FLS памяти:
DWORD FlsAlloc( PFLS_CALLBACK_FUNCTION lpCallback );
VOID WINAPI FlsCallback( PVOID lpFlsData )
{
...
}
Функция отличается от ее аналога TlsAlloc указателем на специаль-
ную необязательную процедуру FlsCallback, предоставляемую разработ-
чиком. Эта процедура будет вызвана автоматически при освобождении
ячейки FLS памяти (как при завершении волокна, так и при завершении
потока или возникновении ошибки), и разработчик может легко предос-
тавить средства для освобождения памяти, указатели на которую были со-
хранены в ячейках FLS памяти.
Разработка параллельных приложений для ОС Windows
229
int ProcB( void )
{
int i, x;
for ( i = x = 0; i < 100; i++ ) x += iptr[i];
return x;
}
unsigned __stdcall ThreadProc( void *param )
{
ProcA( (int)param );
Sleep( 0 );
if ( ProcB() != 100*(int)param ) { /* ОШИБКА!!! */ }
return 0;
}
int main( void )
{
HANDLE hThread[THREADS];
unsigned dwThread;
int i;
/* создаем новые потоки */
for ( i = 0; i < THREADS; i++ )
hThread[i] = (HANDLE)_beginthreadex(
NULL, 0, ThreadProc, (void*)i, 0, &dwThread
);
/* дождаться завершения созданных потоков */
WaitForMultipleObjects( THREADS, hThread, TRUE, INFINITE );
for ( i = 0; i < THREADS; i++ ) CloseHandle( hThread[i] );
TlsFree( dwTlsData );
return 0;
}
Следует внимательно следить за выделением и освобождением дан-
ных, указатели на которые сохраняются в TLS памяти (как в случае явно-
го управления, так и при использовании _declspec(thread)). Могут воз-
никнуть две потенциально ошибочных ситуации:
1. TLS память резервируется в то время, когда уже существуют по-
токи. Это возможно при явном управлении TLS памятью, и для
существующих потоков будут зарезервированы ячейки, но при-
дется предусмотреть специальные меры для их корректной ини-
циализации или для исключения их использования до этого.
2. Все случаи завершения потока. Если TLS память содержит ка-
кие-либо указатели, то сама TLS память будет освобождена, а
228
CIL и системное программирование в Microsoft .NET
228                          CIL и системное программирование в Microsoft .NET   Разработка параллельных приложений для ОС Windows                   229


      int ProcB( void )                                                                    вот те данные, указатели на которые хранились в TLS памяти, –
      {                                                                                    нет. Необходимо специально отслеживать все возможные слу-
        int i, x;                                                                          чаи завершения потоков, включая завершение по ошибке, и
        for ( i = x = 0; i < 100; i++ ) x += iptr[i];                                      принимать меры для освобождения выделенной памяти. При
        return x;                                                                          использовании _declspec(thread) эта ситуация встречается ре-
      }                                                                                    же, так как позволяет хранить в _TLS сегментах данные любого
                                                                                           фиксированного размера.
      unsigned __stdcall ThreadProc( void *param )                                    Следует отметить еще один нюанс, связанный с использованием TLS
      {                                                                          памяти, волокон и оптимизации. В частных случаях волокна могут испол-
        ProcA( (int)param );                                                     няться разными потоками – при этом одно и то же волокно должно иметь
        Sleep( 0 );                                                              доступ к TLS памяти именно того потока, в котором оно в данный момент
        if ( ProcB() != 100*(int)param ) { /* ОШИБКА!!! */ }                     исполняется. А если компилятор генерирует оптимизированный код, то
        return 0;                                                                он может разместить указатель на данные TLS памяти в каком-либо реги-
      }                                                                          стре или временной переменной, что при переключении волокна на дру-
                                                                                 гой поток приведет к ошибке – будет использована TLS память предыду-
     int main( void )                                                            щего потока. Чтобы избежать такой ситуации, компилятору можно ука-
     {                                                                           зать специальный ключ /GT, отключающий некоторые виды оптимизации
       HANDLE      hThread[THREADS];                                             при работе с TLS памятью. Это может потребоваться в крайне редких слу-
       unsigned dwThread;                                                        чаях – когда приложение использует несколько волокон, исполняемых в
       int         i;                                                            нескольких потоках, и при этом волокна должны использовать TLS па-
       /* создаем новые потоки */                                                мять потоков.
       for ( i = 0; i < THREADS; i++ )                                                Аналогично TLS памяти, Windows поддерживает память, локальную
          hThread[i] = (HANDLE)_beginthreadex(                                   для волокон, – так называемую FLS память, или Fiber Local Storage. При
             NULL, 0, ThreadProc, (void*)i, 0, &dwThread                         этом FLS память не зависит от того, какой именно поток выполняет дан-
          );                                                                     ную нить. Для работы с FLS памятью Windows предоставляет набор функ-
       /* дождаться завершения созданных потоков */                              ций, аналогичный Tls-функциям, отличие заключается только в функции
       WaitForMultipleObjects( THREADS, hThread, TRUE, INFINITE );               выделения ячейки FLS памяти:
       for ( i = 0; i < THREADS; i++ ) CloseHandle( hThread[i] );
       TlsFree( dwTlsData );                                                          DWORD FlsAlloc( PFLS_CALLBACK_FUNCTION lpCallback );
       return 0;
     }                                                                                VOID WINAPI FlsCallback( PVOID lpFlsData )
     Следует внимательно следить за выделением и освобождением дан-                   {
ных, указатели на которые сохраняются в TLS памяти (как в случае явно-                  ...
го управления, так и при использовании _declspec(thread)). Могут воз-                 }
никнуть две потенциально ошибочных ситуации:                                          Функция отличается от ее аналога TlsAlloc указателем на специаль-
       1. TLS память резервируется в то время, когда уже существуют по-          ную необязательную процедуру FlsCallback, предоставляемую разработ-
          токи. Это возможно при явном управлении TLS памятью, и для             чиком. Эта процедура будет вызвана автоматически при освобождении
          существующих потоков будут зарезервированы ячейки, но при-             ячейки FLS памяти (как при завершении волокна, так и при завершении
          дется предусмотреть специальные меры для их корректной ини-            потока или возникновении ошибки), и разработчик может легко предос-
          циализации или для исключения их использования до этого.               тавить средства для освобождения памяти, указатели на которую были со-
       2. Все случаи завершения потока. Если TLS память содержит ка-             хранены в ячейках FLS памяти.
          кие-либо указатели, то сама TLS память будет освобождена, а