Najważniejsze wskazówki ogólne w czasie wykonywania współbieżności
W tym dokumencie opisano najważniejsze wskazówki, które stosuje się do wielu obszarów Runtime współbieżności.
Sekcje
Ten dokument zawiera następujące sekcje:
Użyj współpracy konstrukcje synchronizacji, gdy to możliwe
Uniknąć długich zadań, które nie dają
Umożliwia przesunięcie blokować lub mają duże opóźnienia operacji Nadsubskrypcji
Użyj równoczesnych funkcje zarządzania pamięci, gdy to możliwe
Użyj RAII zarządzać okresem istnienia obiektów współbieżności
Nie należy tworzyć obiekty współbieżności na globalne
Nie należy używać obiektów współbieżności w segmenty danych udostępnionych
Użyj współpracy konstrukcje synchronizacji, gdy to możliwe
Runtime współbieżności zawiera wiele konstrukcje współbieżności bezpieczne, nie wymagające obiekt zewnętrzny synchronizacji.Na przykład concurrency::concurrent_vector klasy zapewnia bezpieczny współbieżności dołączania i element dostęp.Jednakże w przypadkach gdy wymagają wyłącznego dostępu do zasobu, środowiska wykonawczego zawiera concurrency::critical_section, concurrency::reader_writer_lock, i concurrency::event klasy.Te typy działają wspólnie; Harmonogram zadań można alokacja zasobów przetwarzania kontekst, jako pierwsze zadanie oczekuje na dane.Jeśli to możliwe, należy użyć tych typów synchronizacji zamiast innych mechanizmów synchronizacji, takich jak dostarczone przez interfejs API systemu Windows, które nie działają wspólnie.Aby uzyskać więcej informacji o tych typach synchronizacji i przykład kodu, zobacz Synchronizacja struktury danych i Porównanie synchronizacji struktur danych do systemu Windows API.
Top
Uniknąć długich zadań, które nie dają
Ponieważ Harmonogram zadań zachowuje się wspólnie, nie zapewnia uczciwości między zadaniami.Dlatego zadania można uruchamianiu innych zadań.Chociaż jest to dopuszczalne w niektórych przypadkach, w innych przypadkach może to spowodować zakleszczenia lub zablokowania.
W następującym przykładzie wykonywana więcej zadań niż liczba zasobów przydzielonych przetwarzania.Pierwsze zadanie nie dają harmonogram zadań i dlatego drugie zadanie nie rozpoczynało się przed zakończeniem pierwszego zadania.
// 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();
}
Ten przykład generuje następujące wyniki:
1: 250000000
1: 500000000
1: 750000000
1: 1000000000
2: 250000000
2: 500000000
2: 750000000
2: 1000000000
Aby włączyć współpracę między dwoma zadaniami na wiele sposobów.Jednym ze sposobów jest czasami plon do harmonogramu zadań w długim zadania.Poniższy przykład modyfikuje task funkcji do wywołania concurrency::Context::Yield metoda plon wykonywanie do harmonogramu zadań, dzięki czemu można uruchomić innego zadania.
// 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();
}
Ten przykład generuje następujące wyniki:
1: 250000000
2: 250000000
1: 500000000
2: 500000000
1: 750000000
2: 750000000
1: 1000000000
2: 1000000000
Context::Yield Metoda daje tylko inny wątek aktywnego na harmonogram, do której należy dany bieżącego wątku, lekkie zadania lub inny wątek system operacyjny.Ta metoda nie dają do pracy, która jest zaplanowana do wykonania concurrency::task_group lub concurrency::structured_task_group obiektu, ale nie została jeszcze uruchomiona.
Istnieją inne sposoby umożliwiające współpracę pomiędzy długim zadań.Duże zadania można podzielić na mniejsze podzadań.Można także włączyć nadsubskrypcji podczas długich zadań.Nadsubskrypcji pozwala utworzyć więcej wątków niż dostępna liczba wątków sprzętu.Nadsubskrypcji jest szczególnie przydatna, gdy długie zadanie zawiera dużą ilość czasu oczekiwania, na przykład odczytywania danych z dysku lub połączenia sieciowego.Aby uzyskać więcej informacji o zadaniach lekkiego i nadsubskrypcji, zobacz Harmonogram zadań (współbieżności Runtime).
Top
Umożliwia przesunięcie blokować lub mają duże opóźnienia operacji Nadsubskrypcji
Runtime współbieżności zapewnia synchronizacyjne, takich jak concurrency::critical_section, umożliwiających zadania, aby zablokować wspólnie i dają się wzajemnie.Jedno zadanie wspólnie blokuje lub plonów, harmonogram zadań można alokacja zasobów przetwarzania kontekst jako pierwsze zadanie oczekuje na dane.
Istnieją przypadki, w których nie można używać mechanizm blokowania współpracy jest świadczone w czasie wykonywania współbieżności.Na przykład biblioteki zewnętrznej, który służy użyć mechanizmu różnych synchronizacji.Innym przykładem jest wykonać operację, która mogłaby mieć dużą ilość czasu oczekiwania, na przykład, korzystając z interfejsu API systemu Windows ReadFile funkcji do odczytu danych z połączenia sieciowego.W tych przypadkach nadsubskrypcji można włączyć innych zadań do wykonania, gdy zadanie jest bezczynny.Nadsubskrypcji pozwala utworzyć więcej wątków niż dostępna liczba wątków sprzętu.
Należy rozważyć następującą funkcję download, który pobiera plik pod danym adresem URL.W tym przykładzie concurrency::Context::Oversubscribe metodę, aby tymczasowo zwiększyć liczbę aktywnych wątków.
// 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;
}
Ponieważ GetHttpFile funkcja wykonuje operację potencjalnie utajone, nadsubskrypcji, można włączyć innych zadań do wykonania, jak bieżące zadanie oczekuje na dane.Pełną wersję tego przykładu, zobacz Jak: przesunięcie czasu oczekiwania za pomocą Nadsubskrypcji.
Top
Użyj równoczesnych funkcje zarządzania pamięci, gdy to możliwe
Użyj funkcji zarządzania pamięci concurrency::Alloc i concurrency::Free, gdy masz szczegółowymi zadania, które często przydzielają małych obiektów, które mają stosunkowo krótki okres istnienia.Runtime współbieżności posiada oddzielne pamięci podręcznej dla każdego uruchomionego wątku.Alloc i Free funkcji zaalokować i zwolnić pamięć z takie bufory bez użycia blokad lub bariery w pamięci.
Aby uzyskać więcej informacji o tych funkcjach zarządzania pamięci, zobacz Harmonogram zadań (współbieżności Runtime).Na przykład, która korzysta z tych funkcji, zobacz Jak: Użyj Alloc i poprawy wydajności pamięci.
Top
Użyj RAII zarządzać okresem istnienia obiektów współbieżności
Środowisko wykonawcze współbieżności używa obsługi wyjątków do realizacji funkcji takich jak anulowania.W związku z tym należy napisać kod wyjątku bezpiecznego, podczas wywołania w czasie wykonywania lub innej biblioteki, który wywołuje w czasie wykonywania.
Zasobów nabycia jest inicjowania (RAII) deseń jest jednym ze sposobów bezpiecznie zarządzać okresem istnienia obiektu współbieżności w ramach danego zakresu.W obszarze wzorzec RAII struktura danych jest przydzielane na stosie.Struktury danych inicjuje lub nabywa zasobu, gdy jest tworzony i niszczy lub zwalnia tego zasobu, kiedy niszczony jest struktura danych.Deseń RAII gwarancje destruktor nosi przed kończy zakres okalającego.Wzorzec ten jest przydatne, gdy funkcja zawiera wiele return sprawozdania.Wzorzec ten pomaga także napisać kod wyjątku palety.Gdy throw instrukcja powoduje, że stos na odpoczynek, destruktor obiekt RAII jest nazywana; w związku z tym zasób zawsze prawidłowo zostanie usunięty lub zwolnione.
Środowisko wykonawcze definiuje kilka klas, które na przykład użyć deseniu RAII concurrency::critical_section::scoped_lock i concurrency::reader_writer_lock::scoped_lock.Te klasy pomocy są znane jako o zakresie blokad.Klasy te zalety podczas pracy z concurrency::critical_section lub concurrency::reader_writer_lock obiektów.Konstruktora klasy te uzyskuje dostęp do dostarczonego critical_section lub reader_writer_lock obiektu; destruktor zwalnia dostęp do tego obiektu.Ponieważ zakresu blokowania zwalnia dostęp do jej obiektu mutex automatycznie, kiedy niszczony jest, możesz nie ręcznie odblokować obiektu źródłowego.
Należy wziąć pod uwagę następujące klasy account, która jest zdefiniowana przez zewnętrznej biblioteki i nie mogą być modyfikowane.
// 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;
};
W następującym przykładzie wykonywana wielu transakcji na account obiektu równolegle.W przykładzie użyto critical_section obiektu do synchronizowania dostępu do account obiektu, ponieważ account klasa nie jest bezpieczny współbieżności.Każda operacja równoległy używa critical_section::scoped_lock obiekt, aby zagwarantować, że critical_section obiektu jest odblokowany, gdy operacja powiedzie się lub kończy się niepowodzeniem.Jeśli saldo konta jest ujemna, withdraw przez zgłaszanie wyjątku nie powiedzie.
// 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;
}
}
Ten przykład generuje następujące przykładowe dane wyjściowe:
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
Dodatkowe przykłady, używające deseń RAII zarządzać okresem istnienia obiektów współbieżności, zobacz Instruktaż: Usuwanie pracy z wątku interfejsu użytkownika, Jak: klasa kontekstu służy do wprowadzenia w życie wspólnych semafora, i Jak: przesunięcie czasu oczekiwania za pomocą Nadsubskrypcji.
Top
Nie należy tworzyć obiekty współbieżności na globalne
Podczas tworzenia obiektu współbieżności w zakresie globalnym może spowodować problemy, takie jak zakleszczenia lub pamięci naruszenia zasad dostępu występuje w aplikacji.
Na przykład podczas tworzenia obiektu Runtime współbieżności środowiska wykonawczego tworzy harmonogram domyślny dla Ciebie, jeśli nie została jeszcze utworzona.Obiekt runtime, który jest tworzony podczas budowy globalnego obiektu spowoduje odpowiednio runtime utworzyć ten harmonogram domyślny.Jednakże proces ten trwa lock wewnętrznego, które mogą zakłócać inicjowania innych obiektów, które obsługują infrastruktury Runtime współbieżności.Tę blokadę wewnętrzne mogą być wymagane przez inny obiekt infrastruktury, która jeszcze nie została zainicjowana, a zatem może spowodować zakleszczenie występuje w aplikacji.
Poniższy przykład ilustruje tworzenie globalnym concurrency::Scheduler obiektu.Wzorzec ten stosuje się nie tylko do Scheduler klasy, ale wszystkie inne typy, które są dostarczane przez Runtime współbieżności.Zaleca się, że nie przestrzeganie tego wzorca, ponieważ może to spowodować nieoczekiwane zachowanie w aplikacji.
// 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()
{
}
Przykłady w prawidłowy sposób, aby utworzyć Scheduler obiektów, zobacz Harmonogram zadań (współbieżności Runtime).
Top
Nie należy używać obiektów współbieżności w segmenty danych udostępnionych
Runtime współbieżności nie obsługuje obiektów współbieżności w sekcji udostępnionych danych, na przykład sekcji danych, tworzony przez data_seg#pragma dyrektywy.Obiekt współbieżności, który jest udostępniony przez granice procesu może umieścić aparat plików wykonywalnych w stanie niespójnym lub nieprawidłowym.
Top
Zobacz też
Zadania
Jak: Użyj Alloc i poprawy wydajności pamięci
Jak: przesunięcie czasu oczekiwania za pomocą Nadsubskrypcji
Jak: klasa kontekstu służy do wprowadzenia w życie wspólnych semafora
Instruktaż: Usuwanie pracy z wątku interfejsu użytkownika
Koncepcje
Biblioteka desenie równoległe (PPL)
Biblioteka agentów asynchroniczne
Harmonogram zadań (współbieżności Runtime)
Synchronizacja struktury danych
Porównanie synchronizacji struktur danych do systemu Windows API
Najlepszych praktyk w bibliotece desenie równoległe
Najlepszych praktyk w bibliotece agentów asynchroniczne