Interaction de données C ++ entre plusieurs threads

Reportez-vous au blog https://blog.csdn.net/hai008007/article/details/80246437 pour l' organiser et le modifier.

L'interaction des données entre plusieurs threads dans le même processus est inévitable. Les files d'attente et les données partagées sont des moyens courants d'interagir les données entre plusieurs threads. La file d'attente encapsulée est relativement moins sujette aux erreurs et les données partagées sont les plus élémentaires et les plus sujettes aux erreurs. , car cela provoquera un conflit de données, c'est-à-dire que plusieurs threads tenteront de récupérer une ressource en même temps, comme la lecture et l'écriture d'un bloc de mémoire, comme illustré dans l'exemple suivant:

#include <iostream>
#include <thread>
 
using namespace std;
 
#define COUNT 10000
 
void inc(int *p){
    
    
    for(int i = 0; i < COUNT; i++){
    
    
        (*p)++;
    }
}
 
int main()
{
    
    
    int a = 0;
    
    thread ta(inc, &a);
    thread tb(inc, &a);
    
    ta.join();
    tb.join();
    
    cout << " a = " << a << endl;
    return 0;
}

L'exemple ci-dessus est un simple échange de données. On peut voir que deux threads lisent et écrivent l'adresse mémoire de & a en même temps. En apparence, une fois les deux threads exécutés, la valeur finale a devrait être COUNT * 2, mais en fait ce n'est pas le cas, car il peut y avoir deux threads qui doivent accéder à cette mémoire en même temps pour fonctionner , et le fil sera interrompu.phénomène. Pour résoudre ce problème, pour les types de données de base simples tels que les caractères, les entiers, les pointeurs, etc., C ++ fournit la classe de modèle atomique atomic, et pour les objets complexes, il fournit le mécanisme de verrouillage le plus couramment utilisé, tel que la classe d'exclusion mutuelle mutex , serrure de porte lock_guard, serrure unique unique_lock, variable de condition condition_variable, etc.

std :: atomique

Pour les threads, les types atomiques appartiennent aux données de "type de ressource", ce qui signifie que plusieurs threads ne peuvent généralement accéder qu'à une copie d'un seul type atomique.
Par conséquent, dans C ++ 11, les types atomiques ne peuvent être construits qu'à partir de leurs types de paramètres de modèle.
La norme n'autorise pas les types atomiques à effectuer des opérations telles que la construction de copie, la construction de déplacement et l'utilisation de operator =. En fait, les opérations telles que la construction de copie, la construction de déplacement et operator = des modèles de classe atomique sont supprimées par défaut.
Cependant, il est possible de construire des variables du type de paramètre de modèle T à partir des variables de type atomique. En effet, le modèle de classe atomique définit toujours la fonction de conversion de type d'atome <T> vers T. Lorsque cela est nécessaire, le compilateur va implicitement Terminez la conversion du type atomique en son type de paramètre de modèle correspondant.
Le fichier d'en-tête C11 <cstdatomic> définit simplement le type atomique correspondant au type intégré

atomique Types de
atomic_bool booléen
atomic_char carboniser
atomic_schar char signé
atomic_uchar caractère non signé
atomic_int int
atomic_uint entier non signé
atomic_short court
atomic_ushort court non signé
atomic_long longue
atomic_ulong non signé longtemps
atomic_llong long long
atomic_ullong non signé longtemps long
atomic_char16_t char16_t
atomic_char32_t char32_t
atomic_wchar_t wchar_t

Encore une fois, par exemple:

#include <iostream>
#include <thread>
#include <atomic>
 
using namespace std;
 
#define COUNT 10000
 
void inc(atomic<int> *p){
    
    
    for(int i = 0; i < COUNT; i++){
    
    
        (*p)++;
    }
}
 
int main()
{
    
    
    atomic<int> a{
    
    0};
    
    thread ta(inc, &a);
    thread tb(inc, &a);
    
    ta.join();
    tb.join();
    
    cout << " a = " << a << endl;
    return 0;
}

std :: lock_guard

Prenons d'abord un petit exemple:

mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();

Dans ce code, le mutex m garantit que l'accès de la partie clé sharedVariable = getVar (); est séquentiel.
Séquentiel signifie: Dans ce cas particulier, chaque thread accède à la partie clé dans l'ordre.
Le code est simple, mais il est sujet à une impasse. Si la partie critique lève une exception ou si le programmeur oublie simplement de déverrouiller le mutex, un blocage se produira.

En utilisant std :: lock_guard, nous pouvons le faire plus élégamment:

{
    
    
  std::mutex m,
  std::lock_guard<std::mutex> lockGuard(m);
  sharedVariable= getVar();
}

C'est facile. Mais que sont le crochet ouvrant {et le crochet fermant}?
Afin de garantir que le cycle de vie de std :: lock_guard n'est valide que dans ce {}.
En d'autres termes, lorsque le cycle de vie quitte la zone critique, son cycle de vie prend fin.
Pour être précis, à ce moment-là, le destructeur de std :: lock_guard a été appelé, et oui, le mutex a été libéré. Le processus est entièrement automatique. De plus, si getVar () lève une exception lorsque sharedVariable = getVar () libère également le mutex. Bien entendu, la portée du corps ou de la boucle de la fonction limite également le cycle de vie de l'objet.

#include <iostream>
#include <thread>
#include <mutex>
 
using namespace std;
 
#define COUNT 10000

static mutex g_mutex;
 
void inc(int *p){
    
    
    for(int i = 0; i < COUNT; i++){
    
    
    	lock_guard<mutex> lck(g_mutex);
        (*p)++;
    }
}
 
int main()
{
    
    
    int a{
    
    0};
    
    thread ta(inc, &a);
    thread tb(inc, &a);
    
    ta.join();
    tb.join();
    
    cout << " a = " << a << endl;
    return 0;
}

De plus, unique_lock () peut également être utilisé.
Unique_lock est un modèle de classe. Au travail, généralement lock_guard (recommandé); lock_guard remplace lock () et unlock () de mutex; unique_lock est beaucoup plus flexible que lock_guard, moins efficace et prend un peu plus de mémoire. L'utilisation spécifique unique_lock () est expliquée séparément, donc je ne le répéterai pas ici.

std :: mutex

std :: mutex est le mutex le plus basique de C ++ 11. L'objet std :: mutex fournit la caractéristique de propriété exclusive, c'est-à-dire qu'il ne prend pas en charge le verrouillage récursif des objets std :: mutex, tandis que std :: recursive_lock Le mutex l'objet peut être verrouillé récursivement.

#include <iostream>
#include <thread>
#include <mutex>
 
using namespace std;
 
#define COUNT 10000

static mutex g_mutex;
 
void inc(int *p){
    
    
    for(int i = 0; i < COUNT; i++){
    
    
    	g_mutex.lock();
        (*p)++;
        g_mutex.unlock();
    }
}
 
int main()
{
    
    
    int a{
    
    0};
    
    thread ta(inc, &a);
    thread tb(inc, &a);
    
    ta.join();
    tb.join();
    
    cout << " a = " << a << endl;
    return 0;
}

std :: condition_variable

Pour les notifications d'événements entre les threads, C ++ 11 fournit une classe de variable de condition condition_variable (qui peut être considérée comme une encapsulation de pthread_cond_t). L'utilisation de variables de condition permet à un thread d'attendre les notifications d'autres threads (wait, wait_for, wait_until) , ou à un autre Le thread envoie des notifications (notify_one, notify_all). La variable de condition doit être utilisée en conjonction avec le verrou. En attente, car il y a un déverrouillage et un reverrouillage, vous devez utiliser un verrou qui peut être déverrouillé et verrouillé manuellement en attendant , comme unique_lock, mais ne peut pas être utilisé lock_guard, un exemple est le suivant:

#include <thread>
#include <iostream>
#include <condition_variable>

# define THREAD_COUNT 10

using namespace std;
mutex m;
condition_variable cv;

int main(void){
    
    
    thread** t = new thread*[THREAD_COUNT];
    int i;
    for(i = 0; i < THREAD_COUNT; i++){
    
    
	    t[i] = new thread( [](int index){
    
    
	        unique_lock<mutex> lck(m);
	        cv.wait_for(lck, chrono::hours(1000));
	        cout << index << endl;}, i );
            
 	    this_thread::sleep_for( chrono::milliseconds(50) );
    }
    
    for(i = 0; i < THREAD_COUNT; i++){
    
    
	    lock_guard<mutex> _(m);
 	    cv.notify_one();
    }
    
    for(i = 0; i < THREAD_COUNT; i++){
    
    
 	    t[i]->join();
	    delete t[i];
    }
    delete t;
    
    return 0;
}

Après avoir compilé et exécuté le programme et sorti les résultats, vous pouvez voir que l'ordre des variables de condition n'est pas garanti, c'est-à-dire que celui qui appelle wait peut ne pas être réveillé en premier.

std :: promesse / futur

Promise / future peut être utilisé pour effectuer une interaction de données simple entre les threads sans considérer le problème des verrous. Le thread A enregistre les données dans une variable de promesse, et un autre thread B peut les obtenir via get_future () de cette variable de promesse. Valeur, quand le thread A n'a pas encore assigné de valeur dans la variable de promesse, le thread B peut également attendre l'affectation de la variable de promesse:

#include <thread>
#include <iostream>
#include <future>

using namespace std;

promise<string> val;

int main(void){
    
    
    thread ta([](){
    
    
	    future<string> fu = val.get_future();
	    cout << "waiting promise->future" << endl;
	    cout << fu.get() << endl;
    });
    
    thread tb([](){
    
    
	    this_thread::sleep_for( chrono::milliseconds(5000) );
	    val.set_value("promise is set");
    });
    
    ta.join();
    tb.join();
    
    return 0;
}

Une future variable ne peut appeler get () qu'une seule fois. Si vous devez appeler get () plusieurs fois, vous pouvez utiliser shared_future. Vous pouvez également passer des exceptions entre les threads via promise / future.

std :: packaged_task

Si vous combinez un objet appelable avec une promesse, il s'agit de packaged_task, ce qui peut encore simplifier l'opération:

#include <thread>
#include <iostream>
#include <mutex>
#include <future>

using namespace std;

static mutex g_mutex;

int main(void){
    
    
    auto run = [=](int index){
    
     
		{
    
    
	    	lock_guard<mutex> lck(g_mutex);
	    	cout << "tasklet " << index << endl;
		}
		this_thread::sleep_for( chrono::seconds(5) );
		return index * 1000;
    };
    
    packaged_task<int(int)> pt1(run);
    packaged_task<int(int)> pt2(run);
    thread t1( [&](){
    
    pt1(2);} );
    thread t2( [&](){
    
    pt2(3);} );

    int f1 = pt1.get_future().get();
    int f2 = pt2.get_future().get();
    cout << "task result=" << f1 << endl;
    cout << "task result=" << f2 << endl;

    t1.join();
    t2.join();
    
    return 0;
}

std :: async

Vous pouvez également essayer de combiner un packaged_task avec un thread, qui est la fonction async (). Utilisez la fonction async () pour démarrer le code d'exécution et renvoyer un futur objet pour enregistrer la valeur de retour du code. Nous n'avons pas besoin de créer et de détruire explicitement des threads, etc., mais l'implémentation de la bibliothèque C ++ 11 décide quand créer et détruire des threads, et créer plusieurs threads, etc., les exemples sont les suivants:

#include <thread>
#include <iostream>
#include <mutex>
#include <future>
#include <vector>

# define COUNT 1000000

using namespace std;

static long do_sum(vector<long> *arr, size_t start, size_t count){
    
    
    static mutex m;
    long sum = 0;
    
    for(size_t i = 0; i < count; i++){
    
    
	    sum += (*arr)[start + i];
    }
    
    {
    
    
	    lock_guard<mutex> lck(m);
	    cout << "thread " << this_thread::get_id() << ", count=" << count
	        << ", start="<< start << ", sum=" << sum << endl;
    }
    return sum;
}

int main(void){
    
    
    vector<long> data(COUNT);
    for(size_t i = 0; i < COUNT; i++){
    
    
        data[i] = random() & 0xff;
    }
    
    vector< future<long> > result;
    
    size_t ptc = thread::hardware_concurrency() * 2;
    for(size_t batch = 0; batch < ptc; batch++) {
    
    
	    size_t batch_each = COUNT / ptc;
	    if (batch == ptc - 1) {
    
    
	        batch_each = COUNT - (COUNT / ptc * batch);
	    }
	    result.push_back(async(do_sum, &data, batch * batch_each, batch_each));
    }

    long total = 0;
    for(size_t batch = 0; batch < ptc; batch++) {
    
    
	    total += result[batch].get();
    }
    cout << "total=" << total << endl;
    
    return 0;
}

En résumé, voici plusieurs méthodes de multithreading différentes, et les exemples utilisés ne sont que très simples.
Pour plus de détails sur les différentes méthodes spécifiques, voici un lien . Suivi, continuez à apprendre.

Je suppose que tu aimes

Origine blog.csdn.net/qq_24649627/article/details/112557135
conseillé
Classement