Współbieżność środowiska wykonawczego — Najlepsze praktyki ogólne
W tym dokumencie opisano najważniejsze wskazówki, które mają zastosowanie do wielu obszarów środowiska wykonawczego współbieżności.
Sekcje
Ten dokument zawiera następujące sekcje:
Skorzystaj z konstruktów kooperatywnej synchronizacji, jeśli jest to możliwe
Unikaj długich zadań, które nie dają wyniku
Użyj nadsubskrypcji operacji przesunięcia, która blokuje lub odznacza się długim czasem opóźnienia
Skorzystaj z funkcji zarządzania pamięcią współbieżną, jeśli jest to możliwe
Użyj RAII, aby zarządzać okresem istnienia obiektów współbieżnych
Nie twórz obiektów współbieżności w zakresie globalnym
Nie używaj obiektów współbieżności w segmentach współdzielonych danych
Skorzystaj z konstruktów kooperatywnej synchronizacji, jeśli jest to możliwe
Współbieżność środowiska wykonawczego zawiera wiele konstrukcje współbieżność bezpieczny, nie wymagające obiektu zewnętrznego synchronizacji.Na przykład concurrency::concurrent_vector klasa oferuje bezpieczny współbieżność append i element dostępu do operacji.Jednakże, w przypadku gdy wymagają wyłącznego dostępu do zasobu, środowiska wykonawczego zawiera concurrency::critical_section, concurrency::reader_writer_lock, i concurrency::event klas.Te typy zachowują się wspólnie; w związku z tym harmonogram zadań można zmienić alokację zasobów przetwarzania do innego kontekstu, jako pierwsze zadanie czeka na dane.Jeśli to możliwe, zamiast inne mechanizmy synchronizacji, takich jak te dostarczane przez interfejs API systemu Windows, które nie zachowują się wspólnie tych typów synchronizacji.Aby uzyskać więcej informacji dotyczących tych typów synchronizacji i przykładowy kod, zobacz Struktury danych synchronizacji i Porównywanie struktur danych synchronizacji z Windows API.
[U góry]
Unikaj długich zadań, które nie dają wyniku
Ponieważ Harmonogram zadań zachowuje się wspólnie, nie zapewnia ona uczciwości między zadaniami.W związku z tym zadania można uniemożliwić innych zadań uruchamianie.Chociaż jest to dopuszczalne w niektórych przypadkach, w innych przypadkach może to spowodować zakleszczenie lub zablokowania.
Poniższy przykład wykonuje zadania więcej niż liczba przydzielonych przetwarzania zasobów.Pierwsze zadanie nie poddaje się 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
Istnieje kilka sposobów umożliwiających współpracę między dwoma zadaniami.Jednym ze sposobów jest czasami wydajności harmonogramu zadań w zadaniu długim.Poniższy przykład modyfikuje task funkcji do wywołania concurrency::Context::Yield metoda wydajności wykonanie harmonogram zadań, aby 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:
Context::Yield Metoda daje tylko inny wątek aktywnych w usłudze harmonogramu, do której należy dany bieżącego wątku, lekki zadania lub inny wątek systemu operacyjnego.Ta metoda nie dają do pracy, który jest zaplanowane do uruchomienia 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ługie zadanie.Nadsubskrypcji pozwala na utworzenie większej liczby wątków niż dostępna liczba wątków sprzętu.Nadsubskrypcji jest szczególnie przydatna, gdy długie zadanie zawiera dużą ilość czas oczekiwania, na przykład Odczyt danych z dysku lub połączenia sieciowego.Aby uzyskać więcej informacji o zadaniach lekki i nadsubskrypcji, zobacz Harmonogram zadań (współbieżność środowiska wykonawczego).
[U góry]
Użyj nadsubskrypcji operacji przesunięcia, która blokuje lub odznacza się długim czasem opóźnienia
Współbieżność środowiska wykonawczego zawiera synchronizacji pierwotnych, takich jak concurrency::critical_section, które umożliwiają zadań wspólnie blokować i wydajność, ze sobą.Gdy jedno zadanie wspólnie blokuje lub plonów, harmonogram zadań można zmienić alokację zasobów przetwarzania do innego kontekstu jako pierwsze zadanie czeka na dane.
Istnieją przypadki, w których nie można używać spółdzielni mechanizm blokujący dostarczonego przez Runtime współbieżności.Na przykład biblioteki zewnętrznej, którego używasz może użyć mechanizmu różnych synchronizacji.Innym przykładem jest podczas wykonywania operacji, która może mieć dużą ilość czas oczekiwania, na przykład, korzystając z interfejsu API systemu Windows ReadFile funkcji do odczytu danych z połączenia sieciowego.W takich przypadkach nadsubskrypcji można włączyć innych zadań do wykonania, gdy drugie zadanie jest bezczynny.Nadsubskrypcji pozwala na utworzenie większej liczby wątków niż dostępna liczba wątków sprzętu.
Należy wziąć pod uwagę następujące funkcja, download, który pobiera plik pod określonym adresem URL.W poniższym przykładzie użyto concurrency::Context::Oversubscribe metoda, 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 jako bieżące zadanie czeka na dane.Aby uzyskać pełną wersję tego przykładu, zobacz Porady: używanie nadsubskrypcji do przesuwania opóźnienia.
[U góry]
Skorzystaj z funkcji zarządzania pamięcią współbieżną, jeśli jest to możliwe
Użyj funkcji zarządzania pamięcią, concurrency::Alloc i concurrency::Free, gdy masz drobnoziarnistych zadań, które często przydzielają małych obiektów, które mają stosunkowo krótki okres istnienia.W czasie wykonywania współbieżność posiada oddzielne pamięci podręcznej dla każdego uruchomionego wątku.Alloc i Free funkcje przydzielić i zwolnić pamięć z tych pamięci podręcznych bez użycia blokad lub bariery pamięci.
Aby uzyskać więcej informacji dotyczących tych funkcji zarządzania pamięcią dostępnych, zobacz Harmonogram zadań (współbieżność środowiska wykonawczego).Na przykład, która korzysta z tych funkcji, zobacz Porady: używanie z funkcji Alloc i Free do poprawiania wydajności pamięci.
[U góry]
Użyj RAII, aby zarządzać okresem istnienia obiektów współbieżnych
Środowisko wykonawcze współbieżność używa do realizacji funkcji, takich jak anulowanie obsługi wyjątków.W związku z tym napisać kod zarządzany wyjątek podczas wywołania w czasie wykonywania lub innej biblioteki, który wywołuje w czasie wykonywania.
Inicjowania jest przejęcie zasobu (RAII) wzór jest jednym ze sposobów bezpiecznie zarządzać okresem istnienia obiektów współbieżności w danym zakresie.W obszarze wzorzec RAII struktury danych jest przydzielany na stosie.Że struktura danych inicjuje lub nabywa zasobu, gdy jest tworzony i niszczy lub zwalnia tego zasobu, kiedy niszczony jest struktura danych.Wzór RAII gwarantuje, że destruktor Nazywa się przed zasięgu kończy pracę.Ten wzór jest przydatne, gdy funkcja zawiera wiele return instrukcji.Ten wzór pomaga także napisać kod zarządzany wyjątek.Gdy throw instrukcja powoduje stosu na odpoczynek, destruktor obiektu RAII jest nazywana; w związku z tym zasób jest zawsze poprawnie usunięty lub zwolnione.
Środowisko wykonawcze definiuje kilka klas, które za pomocą wzoru RAII, na przykład concurrency::critical_section::scoped_lock i concurrency::reader_writer_lock::scoped_lock.Te klasy pomocy są znane jako o zakresie blokad.Klasy te zapewniają wiele korzyści podczas pracy z concurrency::critical_section lub concurrency::reader_writer_lock obiektów.Konstruktor tych klas uzyskuje dostęp do pod warunkiem critical_section lub reader_writer_lock obiektu; destruktor zwalnia dostęp do danego obiektu.Ponieważ zakresu blokowania dostępu do jego obiektu mutex zwalnia automatycznie, gdy ulega zniszczeniu, 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 biblioteki zewnętrznej 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;
};
Poniższy przykład wykonuje wiele transakcji na account obiektu równolegle.W przykładzie użyto critical_section obiekt, aby zsynchronizować dostęp do account obiektu, ponieważ account klasa nie jest bezpieczny dla współbieżności.Każda operacja równoległe używa critical_section::scoped_lock obiekt, aby zagwarantować, że critical_section obiekt jest odblokowany, gdy operacja zakończy się pomyślnie lub nie powiedzie się.Gdy saldo konta jest ujemna, withdraw operacja nie powiedzie się przez Zgłaszanie wyjątku.
// 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:
Dodatkowe przykłady korzystających ze wzorcem RAII zarządzać okresem istnienia obiektów współbieżności, zobacz Wskazówki: usuwanie pracy z wątku interfejs użytkownika, Porady: korzystanie z klasy kontekstu do wdrażania a kooperatywnego semafora, i Porady: używanie nadsubskrypcji do przesuwania opóźnienia.
[U góry]
Nie twórz obiektów współbieżności w zakresie globalnym
Podczas tworzenia obiektu współbieżności w zakresie globalnym może powodować 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ść środowiska wykonawczego tworzy harmonogram domyślny dla Ciebie, jeśli nie został jeszcze utworzony.Obiekt runtime, który jest tworzony podczas budowy obiektu globalnego spowoduje odpowiednio runtime utworzyć ten harmonogram domyślny.Jednak proces ten trwa blokada wewnętrznego może kolidować z inicjalizacją innych obiektów, które obsługuje infrastrukturę Runtime współbieżności.Ta blokada wewnętrznego może być wymagana przez inny obiekt infrastruktury, która jeszcze nie została zainicjowana, a tym samym może spowodować zakleszczenie występuje w aplikacji.
Poniższy przykład ilustruje tworzenie globalnym concurrency::Scheduler obiektu.Ten wzór ma zastosowanie nie tylko do Scheduler klasy, ale wszystkie inne typy, które są dostarczane przez program obsługi współbieżności.Firma Microsoft zaleca, aby nie zastosujesz 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 prawidłowego sposobu tworzenia Scheduler obiektów, zobacz Harmonogram zadań (współbieżność środowiska wykonawczego).
[U góry]
Nie używaj obiektów współbieżności w segmentach współdzielonych danych
Runtime współbieżność nie obsługuje obiektów współbieżności w sekcji udostępnionych danych, na przykład sekcji danych utworzony przez data_seg#pragma dyrektywy. Obiekt współbieżności, który jest współużytkowany przez granice procesu może umieścić środowisko wykonawcze w stanie niespójne lub nieprawidłowe.
[U góry]
Zobacz też
Zadania
Porady: używanie z funkcji Alloc i Free do poprawiania wydajności pamięci
Porady: używanie nadsubskrypcji do przesuwania opóźnienia
Porady: korzystanie z klasy kontekstu do wdrażania a kooperatywnego semafora
Wskazówki: usuwanie pracy z wątku interfejs użytkownika
Koncepcje
Biblioteka równoległych wzorców (PLL)
Biblioteka agentów asynchronicznych
Harmonogram zadań (współbieżność środowiska wykonawczego)
Struktury danych synchronizacji
Porównywanie struktur danych synchronizacji z Windows API
Biblioteka wzorów równoległych — Najlepsze praktyki
Biblioteka agentów asynchronicznych — Najlepsze praktyki