Sdílet prostřednictvím


Obecné doporučené postupy v souběžném běhu

Tento dokument popisuje doporučené postupy, které se vztahuje na více oblastí Runtime souběžnosti.

Oddíly

Tento dokument obsahuje následující oddíly:

  • Pomocí spolupráce konstrukce synchronizace, pokud je to možné

  • Dlouhé úkoly, které nepřinese vyhnout

  • Posun blokovat a mají dlouhou čekací dobou operací pomocí podávání

  • Pokud možno souběžných funkce správy paměti

  • Slouží ke správě životnosti objektů souběžnost RAII

  • Vytvořit objekty souběžnosti na globálním rozsahem

  • Nepoužívejte souběžnosti objekty sdílených datových segmentů

Pomocí spolupráce konstrukce synchronizace, pokud je to možné

Souběžnost Runtime poskytuje mnoho souběžnosti bezpečné konstrukce, které nevyžadují objektu externí synchronizace.Například concurrency::concurrent_vector třída poskytuje souběžnosti bezpečné připojení a přístup operací prvku.Však v případech, kdy vyžaduje výhradní přístup k prostředku, runtime poskytuje concurrency::critical_section, concurrency::reader_writer_lock, a concurrency::event tříd.Tyto typy chovat ve; Proto služba Plánovač úloh zdroje můžete přerozdělit zpracování kontextu jako první úkol čeká data.Pokud je to možné, použijte namísto jiných mechanismů synchronizace, například poskytuje rozhraní API systému Windows, které nefungují ve tyto typy synchronizace.Další informace o těchto typech synchronizace a příklad kódu naleznete v Synchronizace struktury dat a Porovnání struktury synchronizace dat a rozhraní API systému Windows.

Top

Dlouhé úkoly, které nepřinese vyhnout

Protože se chová ve službě Plánovač úloh, neposkytuje spravedlivosti mezi úkoly.Proto úkolu může zabránit další úkoly spuštění.Je to přijatelné v některých případech, v jiných případech to může způsobit zablokování nebo nedostatku.

Následující příklad vykonává více než číslo přidělené prostředky.První úkol nepřinese pro Plánovač úloh, a proto druhý úkol začne až do dokončení prvního úkolu.

// 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();
}

Tento příklad vytvoří následující výstup:

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

Povolení spolupráce mezi dvěma úkoly několika způsoby.Jedním způsobem je příležitostně výnosu pro náročné úlohy Plánovač úloh.Následující příklad změní task funkci volat concurrency::Context::Yield způsob předání spuštění služby Plánovač úloh tak, aby jiný úkol.

// 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();
}

Tento příklad vytvoří následující výstup:

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

Context::Yield Metoda dává pouze jiný podproces aktivní na Plánovač, do kterého patří aktuální podproces lehký úkol nebo jiným podprocesem operačního systému.Tato metoda nepřinese pracovat, je naplánováno spuštění v concurrency::task_group nebo concurrency::structured_task_group objektu, ale ještě nebyl zahájen.

Jiné způsoby spolupráce mezi úkoly dlouhotrvající povolení.Velký úkol lze rozdělit na menší dílčí úkoly.Také můžete povolit podávání během dlouhou úlohu.Jež umožňuje vytvořit více podprocesů, než počet podprocesů hardwaru k dispozici.Podávání je užitečné zejména při dlouhých úloh obsahuje vysoké množství čekací doba, například čtení dat z disku nebo síťové připojení.Další informace o úkolech lehký a jež viz Plánovač úloh (souběžnosti Runtime).

Top

Posun blokovat a mají dlouhou čekací dobou operací pomocí podávání

Souběžnost Runtime poskytuje synchronizace prvky jako concurrency::critical_section, umožňující úkoly ve blokovat a výnos navzájem.Když jeden úkol ve blokuje nebo výnosy, Plánovač úloh zdroje můžete přerozdělit zpracování kontextu jako první úkol čeká data.

Jsou případy, ve kterých nelze použít spolupráce blokovací mechanismus poskytované Runtime souběžnosti.Externí knihovny, můžete použít například použít různé synchronizace mechanismus.Jiným příkladem je při provádění operace, která by mohla mít vysoké částky čekací doba, například při použití rozhraní API systému Windows ReadFile funkci Číst data ze síťového připojení.V těchto případech můžete povolit podávání dalších úloh ke spuštění při nečinnosti jiného úkolu.Jež umožňuje vytvořit více podprocesů, než počet podprocesů hardwaru k dispozici.

Zvažte následující funkce download, která stahuje soubor na dané adrese URL.V tomto příkladu concurrency::Context::Oversubscribe metoda dočasně zvýšit počet podprocesů aktivní.

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

Protože GetHttpFile funkce provádí potenciálně latentní operace, jež povolit další úkoly spouštět jako aktuální úloha čeká na data.Úplnou verzi tohoto příkladu, viz Jak: pomocí zaznamenán posun čekací doba.

Top

Pokud možno souběžných funkce správy paměti

Používat funkce správy paměti, concurrency::Alloc a concurrency::Free, když máte uzamykání úkoly, které často přidělují malé objekty, které mají relativně krátkou životnost.Souběžnost Runtime obsahuje samostatné mezipaměti pro každý spuštěný podproces.Alloc a Free funkce přidělit a uvolnit paměť z těchto mezipamětí bez použití zámků nebo překážky paměti.

Další informace o těchto funkcí správy paměti, viz Plánovač úloh (souběžnosti Runtime).Příklad, který používá tyto funkce, viz Jak: použití Alloc a volné paměti výkon.

Top

Slouží ke správě životnosti objektů souběžnost RAII

Runtime souběžnosti používá k provádění funkcí jako zrušení zpracování výjimek.Proto napište kód výjimky bezpečné při volání do modulu runtime nebo volat jiné knihovny, která volá do modulu runtime.

Prostředku pořízení je inicializace vzorek (RAII) je jeden způsob, jak bezpečně spravovat životnost objektu souběžnosti v daném oboru.Podle vzoru RAII je struktura dat přiřazené v zásobníku.Že struktura dat inicializuje nebo získá zdroje při vytvoření a ničí nebo uvolní prostředku při datovou strukturu je zničen.Vzorek RAII zaručuje, že se objekt je volána před ukončí ohraničujícím oboru.Tento vzorek je užitečné, pokud funkce obsahuje více return příkazy.Tento vzorek také umožňuje psát kód výjimky bezpečné.Když throw příkaz způsobí zásobník unwind, se objekt pro objekt RAII se nazývá; prostředek je proto vždy správně odstraněna nebo uvolněna.

Modul runtime definuje několik tříd, které například použít vzorek RAII concurrency::critical_section::scoped_lock a concurrency::reader_writer_lock::scoped_lock.Tyto pomocné třídy jsou známé jako rozsahem zámky.Tyto třídy poskytují několik výhod při práci s concurrency::critical_section nebo concurrency::reader_writer_lock objektů.Konstruktor těchto tříd získá přístup zadané critical_section nebo reader_writer_lock objektu; vydává se objekt přístup k objektu.Protože scoped uzamčení uvolní přístup k objektu jeho vzájemné vyloučení automaticky při jeho zničení, můžete odemknout ručně základní objekt.

Zvažte následující třídy account, který je definován externí knihovny a proto nelze změnit.

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

Následující příklad provádí více transakcí account objektu paralelně.V příkladu critical_section synchronizovat přístup k objektu account objektu, protože account třída není bezpečné souběžnosti.Každý paralelní operace používá critical_section::scoped_lock objekt zaručit, že critical_section operace úspěšná nebo neúspěšná je odemknutý objekt.Když záporný zůstatek účtu withdraw selhání operace podle došlo k výjimce.

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

Tento příklad vytvoří následující výstup:

Balance before deposit: 1924
Balance after deposit: 2924
Balance before withdrawal: 2924
Balance after withdrawal: -76
Balance before withdrawal: -76
Error details:
        negative balance: -76

Další příklady, které slouží ke správě životnosti objektů souběžnosti vzorek RAII, viz Názorný postup: Odebrání pracovní podproces uživatelského rozhraní, Jak: použití třídy kontext pro provádění spolupráce semafor, a Jak: pomocí zaznamenán posun čekací doba.

Top

Vytvořit objekty souběžnosti na globálním rozsahem

Při vytváření objektu souběžnosti na globální rozsah může způsobit problémy, jako je například paměť nebo zablokování narušení přístupu v aplikaci.

Například při vytváření objektu souběžnosti Runtime modulu runtime vytvoří výchozí Plánovač pro vás, pokud jeden nebyl dosud vytvořen.Objekt runtime, který je vytvořen během vytváření globálních objektů proto způsobí runtime této výchozí Plánovač vytvořit.Tento proces však trvá vnitřní zámek, které mohou narušit inicializace jiné objekty, které podporují souběžnosti Runtime infrastruktury.Tento vnitřní zámek může být požadována jiný objekt infrastruktury, která ještě nebyla inicializována a může tak způsobit zablokování aplikace.

Následující příklad ukazuje vytvoření celosvětové concurrency::Scheduler objektu.Se tento vzor týká není pouze Scheduler třídy, ale všechny ostatní typy poskytovaných Runtime souběžnosti.Doporučujeme, protože může způsobit neočekávané chování aplikace není podle tohoto vzoru.

// 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() 
{   
}

Příklady správný způsob vytvoření Scheduler objekty, viz Plánovač úloh (souběžnosti Runtime).

Top

Nepoužívejte souběžnosti objekty sdílených datových segmentů

Souběžnost Runtime nepodporuje použití objektů souběžnosti části sdílených dat, například data oddíl, který je vytvořen data_seg#pragma směrnice.Souběžnost objekt, který je sdílen přes hranice procesu by mohlo modulu runtime v nekonzistentním nebo neplatném stavu.

Top

Viz také

Úkoly

Jak: použití Alloc a volné paměti výkon

Jak: pomocí zaznamenán posun čekací doba

Jak: použití třídy kontext pro provádění spolupráce semafor

Názorný postup: Odebrání pracovní podproces uživatelského rozhraní

Koncepty

Paralelní knihovnu vzorků (PPL)

Asynchronní agenti knihovny

Plánovač úloh (souběžnosti Runtime)

Synchronizace struktury dat

Porovnání struktury synchronizace dat a rozhraní API systému Windows

Doporučené postupy v paralelní knihovny vzorků

Doporučené postupy v knihovně asynchronní agenti

Další zdroje

Doporučené postupy Runtime souběžnosti