Interação de dados C ++ entre vários threads

Consulte o blog https://blog.csdn.net/hai008007/article/details/80246437 para organizá-lo e modificá-lo.

A interação de dados entre vários threads no mesmo processo é inevitável. Filas e dados compartilhados são maneiras comuns de obter interação de dados entre vários threads. A fila encapsulada é relativamente menos propensa a erros de usar, e os dados compartilhados são os mais básicos e propensos a erros. , porque causará contenção de dados, ou seja, mais de um thread tenta pegar um recurso ao mesmo tempo, como lendo e gravando um bloco de memória, conforme mostrado no exemplo a seguir:

#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;
}

O exemplo acima é uma troca de dados simples.Pode ser visto que duas threads leem e escrevem o endereço de memória de & a ao mesmo tempo. Superficialmente, depois que as duas threads são executadas, o valor final deve ser COUNT * 2, mas na verdade não é o caso, porque pode haver duas threads que precisam acessar este pedaço de memória ao mesmo tempo para a operação , e a discussão será interrompida. Para resolver este problema, para tipos básicos simples de dados, como caracteres, inteiros, ponteiros, etc., C ++ fornece a classe de modelo atômica atômica, e para objetos complexos, fornece o mecanismo de bloqueio mais comumente usado, como mutex de classe de exclusão mútua , lock_guard lock da porta, lock exclusivo unique_lock, variável de condição condition_variable, etc.

std :: atomic

Para encadeamentos, os tipos atômicos pertencem aos dados do "tipo de recurso", o que significa que vários encadeamentos geralmente podem acessar apenas uma cópia de um único tipo atômico.
Portanto, no C ++ 11, os tipos atômicos só podem ser construídos a partir de seus tipos de parâmetro de modelo.
O padrão não permite que tipos atômicos realizem operações como construção de cópia, construção de movimentação e uso de operator =. Na verdade, operações como construção de cópia, construção de movimentação e operator = de modelos de classe atômica são excluídas por padrão.
No entanto, é possível construir variáveis ​​do tipo de parâmetro de modelo T a partir das variáveis ​​do tipo atômico. Isso ocorre porque o modelo de classe atômica sempre define a função de conversão de tipo de <T> atômico para T. Quando necessário, o compilador irá implicitamente Conclua a conversão do tipo atômico para o tipo de parâmetro de modelo correspondente.
O arquivo de cabeçalho C11 <cstdatomic> simplesmente define o tipo atômico correspondente ao tipo embutido

atômico Tipos de
atomic_bool bool
atomic_char Caracteres
atomic_schar char assinado
atomic_uchar caracter não identifcado
atomic_int int
atomic_uint int sem sinal
atomic_short baixo
atomic_ushort curto sem sinal
atomic_long grande
atomic_ulong longo sem sinal
atomic_llong longo longo
atomic_ullong longo sem sinal
atomic_char16_t char16_t
atomic_char32_t char32_t
atomic_wchar_t wchar_t

Novamente, por exemplo:

#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

Vamos dar um pequeno exemplo primeiro:

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

Neste código, o mutex m garante que o acesso da parte da chave sharedVariable = getVar (); seja sequencial.
Sequencial significa: neste caso especial, cada thread obtém acesso à parte principal em ordem.
O código é simples, mas está sujeito a deadlock. Se a parte crítica lançar uma exceção ou o programador simplesmente esquecer de desbloquear o mutex, ocorrerá um deadlock.

Usando std :: lock_guard, podemos fazer isso de forma mais elegante:

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

É fácil. Mas quais são os colchetes de abertura {e os colchetes de fechamento}?
Para garantir que o ciclo de vida de std :: lock_guard seja válido apenas neste {}.
Em outras palavras, quando o ciclo de vida deixa a zona crítica, seu ciclo de vida termina.
Para ser mais preciso, naquele momento, o destruidor de std :: lock_guard foi chamado e, sim, o mutex foi lançado. O processo é totalmente automático. Além disso, se getVar () lança uma exceção quando sharedVariable = getVar () também libera o mutex. Obviamente, o escopo do corpo ou loop da função também limita o ciclo de vida do objeto.

#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;
}

Além disso, unique_lock () também pode ser usado.
Unique_lock é um modelo de classe. No trabalho, geralmente lock_guard (recomendado); lock_guard substitui lock () e unlock () do mutex; unique_lock é muito mais flexível do que lock_guard, menos eficiente e ocupa um pouco mais de memória. O uso específico de unique_lock () é explicado separadamente, portanto, não o repetirei aqui.

std :: mutex

std :: mutex é o mutex mais básico em C ++ 11. O objeto std :: mutex fornece a característica de propriedade exclusiva - isto é, ele não suporta o bloqueio recursivo de objetos std :: mutex, enquanto std :: recursive_lock O mutex objeto pode ser bloqueado recursivamente.

#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

Para notificações de eventos entre threads, C ++ 11 fornece uma classe de variável de condição condition_variable (que pode ser considerada como um encapsulamento de pthread_cond_t). O uso de variáveis ​​de condição permite que uma thread aguarde notificações de outras threads (wait, wait_for, wait_until) , ou para outro O thread envia notificações (notificar_one, notificar_all). A variável de condição deve ser usada em conjunto com o bloqueio. Ao aguardar, porque há desbloqueio e relocking, você deve usar um bloqueio que pode ser desbloqueado manualmente e bloqueado enquanto espera , como unique_lock, mas não pode ser usado lock_guard, um exemplo é o seguinte:

#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;
}

Depois de compilar e executar o programa e gerar os resultados, você pode ver que a ordem das variáveis ​​de condição não é garantida, ou seja, aquela que chama a espera primeiro pode não ser ativada primeiro.

std :: promessa / futuro

Promise / future pode ser usado para realizar interação de dados simples entre threads sem considerar o problema de bloqueios. Thread A salva os dados em uma variável de promessa e outro thread B pode obtê-los por meio de get_future () desta variável de promessa. Valor, quando o encadeamento A ainda não atribuiu um valor na variável de promessa, o encadeamento B também pode esperar pela atribuição da variável de promessa:

#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;
}

Uma variável futura só pode chamar get () uma vez. Se você precisar chamar get () várias vezes, pode usar shared_future. Você também pode passar exceções entre threads por meio de promessa / futuro.

std :: packaged_task

Se você combinar um objeto que pode ser chamado com uma promessa, é packaged_task, o que pode simplificar ainda mais a operação:

#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

Você também pode tentar combinar um packaged_task com um thread, que é a função async (). Use a função async () para iniciar o código de execução e retornar um objeto futuro para salvar o valor de retorno do código. Não precisamos criar e destruir threads explicitamente, etc., mas a implementação da biblioteca C ++ 11 decide quando criar e destruir threads e Criar vários threads, etc., os exemplos são os seguintes:

#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;
}

Em resumo, o acima são vários métodos de multithreading diferentes, e os exemplos usados ​​são apenas muito simples.
Para obter detalhes sobre os diferentes métodos específicos, aqui está um link . Acompanhamento, continue a aprender.

Acho que você gosta

Origin blog.csdn.net/qq_24649627/article/details/112557135
Recomendado
Clasificación