Éviter les conflits de tas
Les gestionnaires de chaînes par défaut fournis par MFC et ATL sont des wrappers simples sur un tas global. Ce tas global est entièrement thread-safe, ce qui signifie que plusieurs threads peuvent allouer et libérer de la mémoire simultanément sans endommager le tas. Pour vous aider à assurer la sécurité des threads, le tas doit sérialiser l’accès à lui-même. Cela s’effectue généralement avec une section critique ou un mécanisme de verrouillage similaire. Chaque fois que deux threads essaient d’accéder simultanément au tas, un thread est bloqué jusqu’à ce que la demande de l’autre thread soit terminée. Pour de nombreuses applications, cette situation se produit rarement et l’impact sur les performances du mécanisme de verrouillage du tas est négligeable. Toutefois, pour les applications qui accèdent fréquemment au tas à partir de plusieurs contentions de threads pour le verrou du tas, l’application peut s’exécuter plus lentement que si elle était monothread (même sur des ordinateurs avec plusieurs processeurs).
Les applications qui utilisent CStringT sont particulièrement sensibles à la contention de tas, car les opérations sur CStringT
les objets nécessitent fréquemment la réaffectation de la mémoire tampon de chaîne.
Une façon d’atténuer la contention de tas entre les threads consiste à faire en sorte que chaque thread alloue des chaînes à partir d’un segment de mémoire local de thread privé. Tant que les chaînes allouées avec l’allocateur d’un thread particulier sont utilisées uniquement dans ce thread, l’allocateur n’a pas besoin d’être thread-safe.
Exemple
L’exemple ci-dessous illustre une procédure de thread qui alloue son propre tas privé non thread-safe à utiliser pour les chaînes sur ce thread :
DWORD WINAPI WorkerThreadProc(void* pBase)
{
// Declare a non-thread-safe heap just for this thread:
CWin32Heap stringHeap(HEAP_NO_SERIALIZE, 0, 0);
// Declare a string manager that uses the thread's heap:
CAtlStringMgr stringMgr(&stringHeap);
int nBase = *((int*)pBase);
int n = 1;
for(int nPower = 0; nPower < 10; nPower++)
{
// Use the thread's string manager, instead of the default:
CString strPower(&stringMgr);
strPower.Format(_T("%d"), n);
_tprintf_s(_T("%s\n"), strPower);
n *= nBase;
}
return(0);
}
Commentaires
Plusieurs threads peuvent être en cours d’exécution à l’aide de cette même procédure de thread, mais étant donné que chaque thread a son propre tas, il n’y a aucune contention entre les threads. En outre, le fait que chaque tas n’est pas thread-safe offre une augmentation mesurable des performances même si une seule copie du thread est en cours d’exécution. Il s’agit du résultat du tas qui n’utilise pas d’opérations interblocées coûteuses pour se protéger contre l’accès simultané.
Pour une procédure thread plus complexe, il peut être pratique de stocker un pointeur vers le gestionnaire de chaînes du thread dans un emplacement TLS (Thread Local Storage). Cela permet à d’autres fonctions appelées par la procédure thread d’accéder au gestionnaire de chaînes du thread.