Compartilhar via


Práticas recomendadas gerais em tempo de execução de concorrência

Este documento descrevem as práticas recomendadas que se aplicam às várias áreas de tempo de execução de simultaneidade.

Seções

Este documento contém as seções a seguir:

  • Use compilações cooperativas de sincronização quando possível

  • Evite as tarefas que não produzem longas

  • Use a sobresubscrição para deslocar as operações que apenas têm ou alta latência dependendo de alta

  • Use funções de gerenciamento de memória simultâneas quando possível

  • Use RAII para gerenciar o tempo de vida de objetos de concorrência

  • Não crie objetos de concorrência no escopo global

  • Não use objetos de simultaneidade em segmentos de dados compartilhados

Use compilações cooperativas de sincronização quando possível

O tempo de execução de simultaneidade fornece muitas construções simultaneidade- segurança que não requerem um objeto externo de sincronização.Por exemplo, a classe de concurrency::concurrent_vector fornece simultaneidade- seguro e acrescenta operações de acesso do elemento.No entanto, para casos onde você requer acesso exclusivo a um recurso, o tempo de execução fornece concurrency::critical_section, concurrency::reader_writer_lock, e classes de concurrency::event .Esses tipos cooperativa; se comportam como consequência, o agendador de tarefa pode realocar recursos de processamento para outro contexto como a primeira tarefa espera dados.Quando possível, use esses tipos de sincronização em vez de outros mecanismos de sincronização, tais como aquelas fornecidas pelo Windows API, que não se comportam cooperativa.Para obter mais informações sobre esses tipos de sincronização e um exemplo de código, consulte Estruturas de dados de sincronização e Comparando estruturas de dados de sincronização à API.

Superior[]

Evite as tarefas que não produzem longas

Porque o agendador de tarefa se comporta cooperativa, não fornece a equidade entre tarefas.Como consequência, uma tarefa pode impedir que outras tarefas comecem.Embora isso seja aceitável em alguns casos, em outros casos isso pode causar um deadlock ou a inanição.

O seguinte exemplo realiza tarefas mais do que o número de recursos alocados para processamento.A primeira tarefa não produz ao agendador de tarefas e consequentemente a segunda tarefa não começa até a primeira tarefa termina.

// cooperative-tasks.cpp
// compile with: /EHsc
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

// Data that the application passes to lightweight tasks.
struct task_data_t
{
   int id;  // a unique task identifier.
   event e; // signals that the task has finished.
};

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

int wmain()
{
   // For illustration, limit the number of concurrent 
   // tasks to one.
   Scheduler::SetDefaultSchedulerPolicy(SchedulerPolicy(2, 
      MinConcurrency, 1, MaxConcurrency, 1));

   // Schedule two tasks.

   task_data_t t1;
   t1.id = 0;
   CurrentScheduler::ScheduleTask(task, &t1);

   task_data_t t2;
   t2.id = 1;
   CurrentScheduler::ScheduleTask(task, &t2);

   // Wait for the tasks to finish.

   t1.e.wait();
   t2.e.wait();
}

Esse exemplo produz a seguinte saída.

1: 250000000 1: 500000000 1: 750000000 1: 1000000000 2: 250000000 2: 500000000 2: 750000000 2: 1000000000

Há várias maneiras para ativar a cooperação entre as duas tarefas.Uma maneira é produzir ocasionalmente ao agendador de tarefa em uma tarefa de execução demorada.O exemplo a seguir altera a função de task para chamar o método de concurrency::Context::Yield para produzir a execução para agendador de tarefas para que outra tarefa pode executar.

// A lightweight task that performs a lengthy operation.
void task(void* data)
{   
   task_data_t* task_data = reinterpret_cast<task_data_t*>(data);

   // Create a large loop that occasionally prints a value to the console.
   int i;
   for (i = 0; i < 1000000000; ++i)
   {
      if (i > 0 && (i % 250000000) == 0)
      {
         wstringstream ss;
         ss << task_data->id << L": " << i << endl;
         wcout << ss.str();

         // Yield control back to the task scheduler.
         Context::Yield();
      }
   }
   wstringstream ss;
   ss << task_data->id << L": " << i << endl;
   wcout << ss.str();

   // Signal to the caller that the thread is finished.
   task_data->e.set();
}

Esse exemplo produz a seguinte saída.

  

O método de Context::Yield produz apenas outro segmento ativa no agendador ao segmento atual pertence, em uma tarefa leve, ou em outro segmento do sistema operacional.Este método não produz ao trabalho que é agendada para executar em um objeto de concurrency::task_group ou de concurrency::structured_task_group mas ainda não foi iniciado.

Existem outras maneiras para ativar a cooperação entre tarefas longas.Você pode quebrar uma grande tarefa em subtarefas menores.Você também pode habilitar a sobresubscrição durante uma tarefa de execução demorada.A sobresubscrição permite que você crie mais segmentos do que o número de segmentos de hardware disponível.A sobresubscrição é especialmente útil quando uma tarefa de execução demorada contém uma quantidade alta de latência, por exemplo, lê dados de disco ou uma conexão de rede.Para obter mais informações sobre as tarefas e de sobresubscrição leve, consulte Agendador de tarefa (tempo de execução de simultaneidade).

Superior[]

Use a sobresubscrição para deslocar as operações que apenas têm ou alta latência dependendo de alta

O tempo de execução de simultaneidade fornece primitivos de sincronização, como concurrency::critical_section, que permitem tarefas bloquear e produzir cooperativa entre si.Quando os blocos ou as de uma tarefa cooperativa, o agendador de tarefas podem realocar recursos de processamento para outro contexto como a primeira tarefa espera dados.

Há casos em que você não pode usar o mecanismo cooperativo de bloqueio que é fornecido no tempo de execução de simultaneidade.Por exemplo, uma biblioteca externa que usa você pode usar um mecanismo diferente de sincronização.Um exemplo é quando você executar uma operação que poderia ter uma quantidade alta de latência, por exemplo, quando você usar a função do Windows API ReadFile para ler dados de uma conexão de rede.Nesses casos, a sobresubscrição pode ativar outras tarefas executar quando outra tarefa estiver ocioso.A sobresubscrição permite que você crie mais segmentos do que o número de segmentos de hardware disponível.

Considere a função a seguir, download, que faz o download do arquivo no URL especificado.Este exemplo usa o método de concurrency::Context::Oversubscribe temporariamente para aumentar o número de segmentos ativos.

// Downloads the file at the given URL.
string download(const string& url)
{
   // Enable oversubscription.
   Context::Oversubscribe(true);

   // Download the file.
   string content = GetHttpFile(_session, url.c_str());

   // Disable oversubscription.
   Context::Oversubscribe(false);

   return content;
}

Como a função de GetHttpFile executa uma operação potencial de, a sobresubscrição pode ativar outras tarefas executar desde que a tarefa atual espera dados.Para a versão completa deste exemplo, consulte Como: use a sobresubscrição para deslocar a latência.

Superior[]

Use funções de gerenciamento de memória simultâneas quando possível

Use as funções, concurrency::Alloc e concurrency::Freede gerenciamento de memória, quando você tem as tarefas refinados que usa frequentemente objetos pequenos que têm um tempo de vida curta relativamente.O tempo de execução de simultaneidade mantém um cache de memória separado para cada segmento em execução.Funções de Alloc e de Free atributo e liberam a memória desses caches sem o uso de bloqueios ou das barreiras de memória.

Para obter mais informações sobre essas funções de gerenciamento de memória, consulte Agendador de tarefa (tempo de execução de simultaneidade).Para um exemplo que usa essas funções, consulte Como: Use Alloc e o libera para melhorar o desempenho de memória.

Superior[]

Use RAII para gerenciar o tempo de vida de objetos de concorrência

O tempo de execução de simultaneidade usa a manipulação de exceção para implementar recursos como o botão.Como consequência, escreva o código de manipulação quando você chama seguro no tempo de execução ou chama outra biblioteca que chama em tempo de execução.

O padrão Aquisição de recurso é inicialização (RAII) é uma maneira para gerenciar com segurança o tempo de vida de um objeto de simultaneidade em um determinado escopo.No padrão de RAII, uma estrutura de dados é atribuída na pilha.A estrutura de dados ou inicializa obtém um recurso quando é criada e o for ou versões esse recurso quando a estrutura de dados seja destruída.O padrão de RAII garante que o destrutor é chamado antes que o escopo delimitador sair.Esse padrão é útil quando uma função contiver várias instruções de return .Ajuda a este padrão também você escreve o código de manipulação. seguroQuando uma declaração de throw faz com que a pilha desenrole, o destrutor para o objeto de RAII é chamado; como consequência, o recurso corretamente sempre é excluído ou liberado.

O tempo de execução define várias classes que usam o padrão de RAII, por exemplo, concurrency::critical_section::scoped_lock e concurrency::reader_writer_lock::scoped_lock.Essas classes auxiliares são conhecidas como bloqueios o escopo.Essas classes fornecem vários benefícios quando você trabalha com concurrency::critical_section ou objetos de concurrency::reader_writer_lock .O construtor dessas classes obtém acesso a critical_section objeto ou fornecido de reader_writer_lock ; o acesso das versões do destrutor ao objeto.Porque um acesso escopo das versões de bloqueio para seu objeto de exclusão mútua automaticamente quando o é destruído, você não desbloqueia manualmente o objeto subjacente.

Considere a seguinte classe, account, que é definido por uma biblioteca externa e consequentemente não pode ser alterada.

// account.h
#pragma once
#include <exception>
#include <sstream>

// Represents a bank account.
class account
{
public:
   explicit account(int initial_balance = 0)
      : _balance(initial_balance)
   {
   }

   // Retrieves the current balance.
   int balance() const
   {
      return _balance;
   }

   // Deposits the specified amount into the account.
   int deposit(int amount)
   {
      _balance += amount;
      return _balance;
   }

   // Withdraws the specified amount from the account.
   int withdraw(int amount)
   {
      if (_balance < 0)
      {
         std::stringstream ss;
         ss << "negative balance: " << _balance << std::endl;
         throw std::exception((ss.str().c_str()));
      }

      _balance -= amount;
      return _balance;
   }

private:
   // The current balance.
   int _balance;
};

O exemplo executa várias transações em um objeto de account paralelamente.O exemplo usa um objeto de critical_section para sincronizar acesso ao objeto de account porque a classe de account não é simultaneidade- segura.Cada operação paralela usa um objeto de critical_section::scoped_lock para garantir que o objeto de critical_section é desbloqueado quando a operação êxito ou falha.Quando o saldo de contas for negativo, a operação de withdraw falhas lançando uma exceção.

// account-transactions.cpp
// compile with: /EHsc
#include "account.h"
#include <ppl.h>
#include <iostream>
#include <sstream>

using namespace concurrency;
using namespace std;

int wmain()
{
   // Create an account that has an initial balance of 1924.
   account acc(1924);

   // Synchronizes access to the account object because the account class is 
   // not concurrency-safe.
   critical_section cs;

   // Perform multiple transactions on the account in parallel.   
   try
   {
      parallel_invoke(
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before deposit: " << acc.balance() << endl;
            acc.deposit(1000);
            wcout << L"Balance after deposit: " << acc.balance() << endl;
         },
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before withdrawal: " << acc.balance() << endl;
            acc.withdraw(50);
            wcout << L"Balance after withdrawal: " << acc.balance() << endl;
         },
         [&acc, &cs] {
            critical_section::scoped_lock lock(cs);
            wcout << L"Balance before withdrawal: " << acc.balance() << endl;
            acc.withdraw(3000);
            wcout << L"Balance after withdrawal: " << acc.balance() << endl;
         }
      );
   }
   catch (const exception& e)
   {
      wcout << L"Error details:" << endl << L"\t" << e.what() << endl;
   }
}

Este exemplo produz a seguinte saída de exemplo:

  

Para exemplos adicionais que usam o padrão de RAII para gerenciar o tempo de vida de objetos de concorrência, consulte Passo a passo: Removendo o trabalho de um encadeamento de interface do usuário, Como: Use a classe de contexto para implementar um semáforo cooperativo, e Como: use a sobresubscrição para deslocar a latência.

Superior[]

Não crie objetos de concorrência no escopo global

Quando você cria um objeto de concorrência no escopo global você pode fazer com que os problemas como violações de acesso de bloqueio completa ou de memória ocorram em seu aplicativo.

Por exemplo, quando você cria um objeto de tempo de execução de concorrência, o tempo de execução cria um agendador padrão para um se você ainda não foi criado.Um objeto em tempo de execução que é criado durante a compilação global do objeto adequadamente fará com que o tempo de execução criar este agendador padrão.No entanto, esse processo terá um bloqueio interno, que pode interferir com a inicialização de outros objetos que oferecem suporte a infraestrutura de tempo de execução de simultaneidade.Este bloqueio interno pode ser necessário por outro objeto de infraestrutura que não é inicializada ainda assim, e pode fazer com que um deadlock ocorre no seu aplicativo.

O exemplo a seguir demonstra a criação de um objeto global de concurrency::Scheduler .Esse padrão se aplica não apenas à classe de Scheduler mas a todos os outros tipos que são fornecidos em tempo de execução de simultaneidade.Recomendamos que você não segue este padrão porque pode causar um comportamento inesperado em seu aplicativo.

// global-scheduler.cpp
// compile with: /EHsc
#include <concrt.h>

using namespace concurrency;

static_assert(false, "This example illustrates a non-recommended practice.");

// Create a Scheduler object at global scope.
// BUG: This practice is not recommended because it can cause deadlock.
Scheduler* globalScheduler = Scheduler::Create(SchedulerPolicy(2,
   MinConcurrency, 2, MaxConcurrency, 4));

int wmain() 
{   
}

Para exemplos da maneira correta de criar objetos de Scheduler , consulte Agendador de tarefa (tempo de execução de simultaneidade).

Superior[]

Não use objetos de simultaneidade em segmentos de dados compartilhados

O tempo de execução de simultaneidade não oferece suporte ao uso de objetos de simultaneidade em uma seção compartilhada de dados, por exemplo, uma seção de dados que foi criada pela política de data_seg#pragma .Um objeto de simultaneidade que é compartilhado através dos limites de processo pode colocar o tempo de execução em um estado inconsistente ou inválido.

Superior[]

Consulte também

Tarefas

Como: Use Alloc e o libera para melhorar o desempenho de memória

Como: use a sobresubscrição para deslocar a latência

Como: Use a classe de contexto para implementar um semáforo cooperativo

Passo a passo: Removendo o trabalho de um encadeamento de interface do usuário

Conceitos

A modelos paralela a biblioteca (PPL)

Biblioteca de agentes assíncrono

Agendador de tarefa (tempo de execução de simultaneidade)

Estruturas de dados de sincronização

Comparando estruturas de dados de sincronização à API

As práticas recomendadas de paralela da biblioteca

Práticas recomendadas na biblioteca assíncrona de agentes

Outros recursos

Práticas recomendadas em tempo de execução de concorrência