web-dev-qa-db-fra.com

comment sécuriser un thread d'application?

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++.

55
ashmish2

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:

  • Identifiez toutes les données partagées entre les threads (si vous les manquez, vous ne pouvez pas les protéger)
  • créer un membre 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).
  • nettoyer les globaux. Les globaux sont mauvais de toute façon, et bonne chance en essayant de faire quoi que ce soit de thread-safe avec les globaux.
  • Méfiez-vous du mot clé 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.
  • Méfiez-vous du paradigme de verrouillage à double vérification. La plupart des gens qui l'utilisent se trompent de manière subtile, et il est susceptible d'être cassé par la mise en garde de bas niveau.

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.

56
Tim

Deux choses:

1. Assurez-vous de ne pas utiliser de globaux. Si vous disposez actuellement de globaux, faites-les membres d'une structure d'état par thread, puis demandez au thread de transmettre la structure aux fonctions communes.

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.

2. Si vos threads ont des données en commun qui doivent être partagées, vous devez examiner les sections critiques et les sémaphores. Chaque fois qu'un de vos threads accède aux données, il doit bloquer les autres threads, puis les débloquer lorsqu'ils ont fini d'accéder aux données partagées.

14
Theo

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!!

3
GutiMac

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.

0
Lalaland