9
votes

empêcher std :: atomique de débordement

J'ai un compteur atomique ( std :: atomic compte code>) qui résiste à incrémenter séquentiellement des valeurs à plusieurs threads.

#include <iostream>
#include <atomic>
#include <limits>
#include <stdexcept>
#include <thread>

std::atomic<uint16_t> count;

uint16_t get_val() // called by multiple threads
{
    uint16_t my_val;
    do
    {
        my_val = count;

        // make sure I get the next value

        if (count.compare_exchange_strong(my_val, my_val + 1))
        {
            // if I got the next value, make sure we don't overflow

            if (my_val == std::numeric_limits<uint16_t>::max())
            {
                count = std::numeric_limits<uint16_t>::max() - 1;
                throw std::runtime_error("count overflow");
            }
            break;
        }

        // if I didn't then check if there are still numbers available

        if (my_val == std::numeric_limits<uint16_t>::max())
        {
            count = std::numeric_limits<uint16_t>::max() - 1;
            throw std::runtime_error("count overflow");
        }

        // there are still numbers available, so try again
    }
    while (1);
    return my_val + 1;
}

void run()
try
{
    while (1)
    {
        if (get_val() == 0)
            exit(1);
    }

}
catch(const std::runtime_error& e)
{
    // overflow
}

int main()
{
    while (1)
    {
        count = 1;
        std::thread a(run);
        std::thread b(run);
        std::thread c(run);
        std::thread d(run);
        a.join();
        b.join();
        c.join();
        d.join();
        std::cout << ".";
    }
    return 0;
}


1 commentaires

uint64_t et problème résolu? :)


3 Réponses :


1
votes

Il me semble qu'il y a toujours une condition de course où comptage sera défini sur 0 de manière momentanément telle qu'un autre thread verra la valeur 0.

supposons que compte est à std :: numeric_limits :: max () et deux threads tentent d'obtenir la valeur incrémentée. Au moment où le thread 1 effectue le comte.compare_exchange_strong (my_val, my_val + 1) , le nombre est défini sur 0 et c'est ce que le thread 2 verra si cela arrive à appeler et à compléter get_val ( ) Avant le thread 1 a une chance de restaurer compter à max () .


0 commentaires

2
votes

Si l'efficacité est une préoccupation importante, je ne suggérerai pas d'être si stricte sur le chèque. Je suppose que dans le dépassement de l'utilisation normale ne sera pas un problème, mais avez-vous vraiment besoin de la gamme complète de 65k (votre exemple utilise UINT16)?

Il serait plus facile si vous supposez un maximum sur le nombre de threads que vous avez avoir couru. Ceci est une limite raisonnable car aucun programme n'a un nombre illimité de concurrence. Donc, si vous avez des threads N code>, vous pouvez simplement réduire votre limite de dépassement à 65K - N Code>. Pour comparer si vous débordez vous n'avez pas besoin d'un CAS: P>

uint16_t current = count.load(std::memory_order_relaxed);
if( current >= (std::numeric_limits<uint16_t>::max() - num_threads - 1) )
    throw std::runtime_error("count overflow");
count.fetch_add(1,std::memory_order_relaxed);


2 commentaires

Je viens d'utiliser uint16_t si un trop-plein potentiel se produit plus tôt. J'aime votre idée, mais le nombre de threads est maintenant connu et la dernière chose que je veux faire est de choisir une limite supérieure magique, puis de le faire dépasser à un moment donné de l'avenir.


@lori, le nombre peut ne pas être connu, mais il doit y avoir une limite supérieure raisonnable. Vous pouvez spécifier 1000 threads, voire 10k si vous le souhaitez. Ou utilisez un uint_32 , puis spécifie un million.



8
votes

Oui, vous devez utiliser CAS CODE> OPERATION.

std::atomic<uint16_t> g_count;

uint16_t get_next() {
   uint16_t new_val = 0;
   do {
      uint16_t cur_val = g_count;                                            // 1
      if (cur_val == std::numeric_limits<uint16_t>::max()) {                 // 2
          throw std::runtime_error("count overflow");
      }
      new_val = cur_val + 1;                                                 // 3
   } while(!std::atomic_compare_exchange_weak(&g_count, &cur_val, new_val)); // 4

   return new_val;
}


0 commentaires