Je pensais que le thread safe, en particulier, signifie qu'il doit satisfaire le besoin de plusieurs threads pour accéder aux mêmes données partagées. Mais il semble que cette définition ne soit pas suffisante.
Quelqu'un peut-il s'il vous plaît énumérer les choses à faire ou à prendre en charge pour sécuriser un thread d'application. Si possible, donnez une réponse concernant le langage C/C++.
Il existe plusieurs façons dont une fonction peut être thread-safe.
Il peut être réentrant . Cela signifie qu'une fonction n'a pas d'état et ne touche aucune variable globale ou statique, elle peut donc être appelée à partir de plusieurs threads simultanément. Le terme vient du fait d'autoriser un thread à entrer dans la fonction alors qu'un autre thread est déjà à l'intérieur.
Il peut avoir une section critique . Ce terme est souvent utilisé, mais je préfère franchement les données critiques . Une section critique se produit chaque fois que votre code touche des données partagées sur plusieurs threads. Je préfère donc mettre l'accent sur ces données critiques.
Si vous utilisez correctement un mutex , vous pouvez synchroniser l'accès aux données critiques, en vous protégeant correctement des modifications de thread non sécurisées. Les mutex et les verrous sont très utiles, mais avec une grande puissance vient une grande responsabilité. Vous ne devez pas verrouiller deux fois le même mutex dans le même thread (c'est-à-dire un autobloquant). Vous devez être prudent si vous acquérez plus d'un mutex, car cela augmente votre risque de blocage. Vous devez toujours protéger vos données avec des mutex.
Si toutes vos fonctions sont thread-safe et toutes vos données partagées correctement protégées, votre application doit être thread-safe.
Comme l'a dit Crazy Eddie, c'est un sujet énorme. Je recommande de lire sur les threads boost et de les utiliser en conséquence.
mise en garde de bas niveau : les compilateurs peuvent réorganiser les instructions, ce qui peut nuire à la sécurité des threads. Avec plusieurs cœurs, chaque cœur possède son propre cache et vous devez synchroniser correctement les caches pour garantir la sécurité des threads. De plus, même si le compilateur ne réorganise pas les instructions, le matériel le pourrait. Ainsi, une sécurité complète et garantie des threads n'est actuellement pas possible. Cependant, vous pouvez obtenir 99,99% du chemin, et un travail est en cours avec les fournisseurs de compilateurs et les fabricants de processeurs pour corriger cette mise en garde persistante.
Quoi qu'il en soit, si vous cherchez une liste de contrôle pour rendre une classe sûre pour les threads:
boost::mutex m_mutex
et l'utiliser chaque fois que vous essayez d'accéder à ces données de membre partagé (idéalement, les données partagées sont privées à la classe, vous pouvez donc être plus certain de les protéger correctement).static
. Ce n'est en fait pas sûr pour les threads. Donc, si vous essayez de faire un singleton, cela ne fonctionnera pas correctement.C'est une liste de contrôle incomplète. J'en ajouterai plus si j'y pense, mais j'espère que cela suffira pour vous aider à démarrer.
Deux choses:
Par exemple, si nous commençons par:
// Globals
int x;
int y;
// Function that needs to be accessed by multiple threads
// currently relies on globals, and hence cannot work with
// multiple threads
int myFunc()
{
return x+y;
}
Une fois que nous ajoutons une structure d'état, le code devient:
typedef struct myState
{
int x;
int y;
} myState;
// Function that needs to be accessed by multiple threads
// now takes state struct
int myFunc(struct myState *state)
{
return (state->x + state->y);
}
Maintenant, vous pouvez vous demander pourquoi ne pas simplement passer x et y comme paramètres. La raison en est que cet exemple est une simplification. Dans la vraie vie, votre structure d'état peut avoir 20 champs et la transmission de la plupart de ces paramètres 4-5 fonctions devient décourageante. Vous préférez passer un paramètre au lieu de plusieurs.
Si vous voulez faire un accès exclusif aux méthodes de la classe, vous devez utiliser un verrou sur ces fonctions.
Les différents types de serrures:
Utilisation de atomic_flg_lck:
class SLock
{
public:
void lock()
{
while (lck.test_and_set(std::memory_order_acquire));
}
void unlock()
{
lck.clear(std::memory_order_release);
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck.clear();
}
private:
std::atomic_flag lck;// = ATOMIC_FLAG_INIT;
};
Utilisation de atomic:
class SLock
{
public:
void lock()
{
while (lck.exchange(true));
}
void unlock()
{
lck = true;
}
SLock(){
//lck = ATOMIC_FLAG_INIT;
lck = false;
}
private:
std::atomic<bool> lck;
};
Utilisation de mutex:
class SLock
{
public:
void lock()
{
lck.lock();
}
void unlock()
{
lck.unlock();
}
private:
std::mutex lck;
};
Juste pour Windows:
class SLock
{
public:
void lock()
{
EnterCriticalSection(&g_crit_sec);
}
void unlock()
{
LeaveCriticalSection(&g_crit_sec);
}
SLock(){
InitializeCriticalSectionAndSpinCount(&g_crit_sec, 0x80000400);
}
private:
CRITICAL_SECTION g_crit_sec;
};
Les atomic et and atomic_flag maintiennent le thread dans un nombre de tours. Mutex ne fait que dormir le fil. Si le temps d'attente est trop long, il vaut peut-être mieux dormir le fil. Le dernier "CRITICAL_SECTION" conserve le fil dans un nombre de tours jusqu'à ce qu'un certain temps soit consommé, puis le fil se met en veille.
Comment utiliser ces sections critiques?
unique_ptr<SLock> raiilock(new SLock());
class Smartlock{
public:
Smartlock(){ raiilock->lock(); }
~Smartlock(){ raiilock->unlock(); }
};
Utilisation de l'idiome raii. Le constructeur pour verrouiller la section critique et le destructeur pour la déverrouiller.
Exemple
class MyClass {
void syncronithedFunction(){
Smartlock lock;
//.....
}
}
Cette implémentation est sécurisée pour les threads et les exceptions, car le verrouillage des variables est enregistré dans la pile, donc lorsque l'étendue de la fonction est terminée (fin de fonction ou exception), le destructeur sera appelé.
J'espère que cela vous sera utile.
Merci!!
Une idée est de considérer votre programme comme un groupe de threads commutant dans les files d'attente. Chaque thread aurait une file d'attente, et ces files d'attente seraient partagées (avec une méthode de synchronisation des données partagée (comme un mutex, etc.)) à tous les threads.
Ensuite, "résolvez" le problème du producteur/consommateur, mais vous voulez empêcher les files d'attente de déborder ou de déborder. http://en.wikipedia.org/wiki/Producer-consumer_problem
Tant que vous gardez vos threads localisés, en partageant simplement des données avec en envoyant des copies sur la file d'attente, et en n'accédant pas à des choses dangereuses pour les threads comme (la plupart) des bibliothèques GUI et des variables statiques dans plusieurs threads, alors ça devrait aller.