Как освободить объект в TLS-слоте при выходе из потока в Windows?

например, в многопоточной программе:

struct TLSObject;

void foo()
{
    TLSObject* p = TlsGetValue(slot);
    if (p == 0) {
        p = new TLSObject;
        TlsSetValue(slot, p);
    }
    // doing something with p
}

первый вызов foo() в любом потоке создаст новый объект TLSObject.

мой вопрос: как удалить TLSObject (если я не использую boost::thread и boost::thread_specific_ptr)?

boost::thread_specific_ptr может выполнять очистку при выходе из потока, но это зависит от boost::thread, я думаю, не для обычного потока ОС, и он медленный.


person amanjiang    schedule 06.09.2013    source источник


Ответы (4)


Вместо TlsAlloc используйте FlsAlloc (и связанные Fls* функции ). С помощью FLS вы регистрируете обратный вызов очистки, который ОС будет вызывать в потоке до того, как поток завершится, что дает вам возможность выполнить очистку.

person James McNellis    schedule 06.09.2013
comment
Джеймс, я знаю, что вы инженер VC, стандартная библиотека и компилятор VC используют Fls или Tls? Всегда ли безопасно использовать Fls вместо Tls (действительно ли это рекомендуется)? Я знаю, что Fls довольно ограничен с точки зрения слотов по сравнению с Tls. - person Fulvio Esposito; 09.08.2015
comment
Последние версии библиотек времени выполнения используют FLS во всех операционных системах, где они доступны (FLS был представлен в Windows Vista). Процессу доступно 128 слотов FLS (FLS_MAXIMUM_AVAILABLE). - person James McNellis; 10.08.2015
comment
Как вы сказали, FLS был представлен в Windows Vista, а как насчет Windows XP? Могу ли я иметь другую альтернативу? - person amanjiang; 13.09.2015
comment
@amanjiang Нет. Windows XP мертва. Я не знаю ни одного решения, которое работает в Windows XP. - person James McNellis; 14.09.2015

Хорошо. Для Windows Vista и выше, как сказал Джеймс Макнеллис, мы могли бы использовать FlsCallback.

Для DLL мы могли бы просто использовать DllMain, если параметр Reason равен DLL_THREAD_DETACH, мы делаем очистку. Альтернативой может быть использование _pRawDllMain, это похоже на другой DllMain, вы можете найти его на источник продвижения.

Для EXE мы могли бы использовать обратный вызов TLS, посмотрите здесь и здесь, и, конечно же, источник ускорения. На практике это работает в Windows XP, но я обнаружил, что оптимизации могут сделать его неэффективным, поэтому будьте осторожны с оптимизациями или делайте явную ссылку на указатель вашей функции обратного вызова.

Сохраните приведенный ниже код в tls.cpp и добавьте его в свой проект, независимо от того, exe это или dll, он будет работать. Обратите внимание, что для DLL в Windows Vista и более поздних версиях функция onThreadExit может быть вызвана дважды — один раз из dll_callback и один из tls_callback.

#include <windows.h>

extern void onThreadExit();

static void NTAPI tls_callback(PVOID, DWORD reason, PVOID)
{
    if (reason == DLL_THREAD_DETACH) {
        onThreadExit();
    }
}

static BOOL WINAPI dll_callback(LPVOID, DWORD reason, LPVOID)
{
    if (reason == DLL_THREAD_DETACH) {
        onThreadExit();
    }
    return TRUE;
}

#pragma section(".CRT$XLY",long,read)
extern "C" __declspec(allocate(".CRT$XLY")) PIMAGE_TLS_CALLBACK _xl_y = tls_callback;

extern "C"
{
    extern BOOL (WINAPI * const _pRawDllMain)(HANDLE, DWORD, LPVOID) = &dll_callback;
}

#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:__xl_y")

Если вы считаете, что это непонятно, используйте at_thread_exit сложность скрыта. Фактически приведенный выше код является упрощенной версией boost tls. И если вы не хотите использовать ускорение, в системе Windows это альтернатива.

Или более общий способ: thread_local.

person amanjiang    schedule 14.09.2015
comment
Очень изобретательно, но предостережение: этот подход ненадежен и может в будущем тихо сломаться (поскольку tls_callback не будет вызываться), он также не гарантирует, что после обратного вызова не будет вызван другой код CRT, который зависит от то, что вы делаете, потенциально может снова выделить TLS. - person rustyx; 02.02.2016

Boost::thread_specific_ptr должен работать в любом потоке (согласно ответу на мой вопрос: Проверить, является ли поток повышающим потоком)

Насчет того, что он медленный, да, он не идеален. Однако вы можете использовать любой нормальный механизм TLS, какой пожелаете (я использовал специальный модификатор GCC), а затем создать дополнительный thread_specific_ptr, который очищает данные (создайте оболочку для вашего истинного указателя TLS). Таким образом, создание и удаление TLS немного дороже, но доступ не затрагивается.

person edA-qa mort-ora-y    schedule 06.09.2013
comment
Я только что провел тест, thread_specific_ptr может работать с _beginthreadex, это хорошая новость. Теперь я использую Win32 TLS для доступа и thread_specific_ptr для очистки, это работает! Эта идея была потрясающей, большое спасибо. - person amanjiang; 06.09.2013