Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här dokumentet beskriver metodtips som gäller för flera områden i Concurrency Runtime.
Sektioner
Det här dokumentet innehåller följande avsnitt:
Använd konstruktioner för samarbetssynkronisering när det är möjligt
Använd översubscription för att kompensera operationer som blockerar eller har hög svarstid
Använd samtidiga minneshanteringsfunktioner när det är möjligt
Använda RAII för att hantera livslängden för samtidighetsobjekt
Använd konstruktioner för samarbetssynkronisering när det är möjligt
Concurrency Runtime innehåller många samtidighetssäkra konstruktioner som inte kräver ett externt synkroniseringsobjekt. Till exempel ger klassen concurrency::concurrent_vector samtidighetssäker tilläggs- och elementåtkomståtgärder. Här innebär samtidighetssäkert att pekare eller iteratorer alltid är giltiga. Det är inte en garanti för elementinitiering eller en viss bläddreringsordning. För fall där du kräver exklusiv åtkomst till en resurs tillhandahåller dock runtimen concurrency::critical_section, concurrency::reader_writer_lock och concurrency::event-klasser. Dessa typer beter sig tillsammans. Därför kan schemaläggaren omallokera bearbetningsresurser till en annan kontext när den första aktiviteten väntar på data. När det är möjligt använder du dessa synkroniseringstyper i stället för andra synkroniseringsmekanismer, till exempel de som tillhandahålls av Windows-API:et, som inte fungerar tillsammans. Mer information om dessa synkroniseringstyper och ett kodexempel finns i Synkronisera datastrukturer och Jämföra synkroniseringsdatastrukturer med Windows-API:et.
[Topp]
Undvik långa uppgifter som inte ger något resultat
Eftersom schemaläggaren beter sig kooperativt ger den inte rättvist mellan uppgifter. Därför kan en aktivitet hindra andra aktiviteter från att starta. Även om detta är acceptabelt i vissa fall kan detta i andra fall orsaka dödläge eller svält.
I följande exempel utförs fler uppgifter än antalet allokerade bearbetningsresurser. Den första aktiviteten visas inte för schemaläggaren och därför startar inte den andra aktiviteten förrän den första aktiviteten är klar.
// 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();
}
Det här exemplet genererar följande utdata:
1: 250000000 1: 500000000 1: 750000000 1: 1000000000 2: 250000000 2: 500000000 2: 750000000 2: 1000000000
Det finns flera sätt att möjliggöra samarbete mellan de två uppgifterna. Ett sätt är att ibland ge plats åt schemaläggaren i en långdragen uppgift. I följande exempel modifieras task funktionen för att anropa metoden concurrency::Context::Yield för att ge schemaläggaren möjlighet att köra en annan uppgift.
// 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();
}
Det här exemplet genererar följande utdata:
1: 250000000
2: 250000000
1: 500000000
2: 500000000
1: 750000000
2: 750000000
1: 1000000000
2: 1000000000
Metoden Context::Yield ger bara en annan aktiv tråd i schemaläggaren som den aktuella tråden tillhör, en lätt uppgift eller en annan operativsystemtråd. Den här metoden leder inte till att arbete som är schemalagt att köras i ett samtidighet::task_group eller samtidighet::structured_task_group objekt men ännu inte har startats.
Det finns andra sätt att möjliggöra samarbete mellan långvariga uppgifter. Du kan dela upp en stor aktivitet i mindre underaktiviteter. Du kan också aktivera översubskription under en lång uppgift. Med överprenumerering kan du skapa fler trådar än det tillgängliga antalet maskinvarutrådar. Överprenumerering är särskilt användbart när en lång aktivitet innehåller en hög fördröjning, till exempel att läsa data från disken eller från en nätverksanslutning. Mer information om lätta uppgifter och överprenumeration finns i Schemaläggaren.
[Topp]
Använd översubscription för att kompensera åtgärder som blockerar eller har hög svarstid
Concurrency Runtime tillhandahåller synkroniseringsprimitiver, till exempel samtidighet::critical_section, som gör det möjligt för aktiviteter att samarbeta genom att blockera och släppa kontroll till varandra. När en aktivitet tillsammans blockerar eller ger resultat kan schemaläggaren omallokera bearbetningsresurser till en annan kontext när den första aktiviteten väntar på data.
Det finns fall där du inte kan använda mekanismen för samarbetsblockering som tillhandahålls av Concurrency Runtime. Ett externt bibliotek som du använder kan till exempel använda en annan synkroniseringsmekanism. Ett annat exempel är när du utför en åtgärd som kan ha en hög fördröjning, till exempel när du använder Windows API-funktionen ReadFile för att läsa data från en nätverksanslutning. I dessa fall kan överprenumerationer göra det möjligt för andra aktiviteter att köras när en annan aktivitet är inaktiv. Med överprenumerering kan du skapa fler trådar än det tillgängliga antalet maskinvarutrådar.
Tänk dig följande funktion, download, som laddar ned filen på den angivna URL:en. I det här exemplet används metoden concurrency::Context::Oversubscribe för att tillfälligt öka antalet aktiva trådar.
// 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;
}
GetHttpFile Eftersom funktionen utför en potentiellt latent åtgärd kan överprenumerering göra det möjligt för andra aktiviteter att köras när den aktuella aktiviteten väntar på data. Den fullständiga versionen av det här exemplet finns i Hur du: Använder överprenumeration för att kompensera för fördröjning.
[Topp]
Använd samtidiga minneshanteringsfunktioner när det är möjligt
Använd minneshanteringsfunktionerna, samtidighet::Alloc och samtidighet::Free, när du har finfördelade uppgifter som ofta allokerar små objekt som har en relativt kort livslängd. Concurrency Runtime innehåller en separat minnescachen för varje tråd som körs. Funktionerna Alloc och Free allokerar och frigör minne från dessa cacheminnen utan att använda lås eller minnesbarriärer.
Mer information om dessa minneshanteringsfunktioner finns i Schemaläggaren. Ett exempel som använder dessa funktioner finns i How to: Use Alloc and Free to Improve Memory Performance (Använda Alloc och Free för att förbättra minnesprestanda).
[Topp]
Använda RAII för att hantera livslängden för samtidighetsobjekt
Concurrency Runtime använder undantagshantering för att implementera funktioner som annullering. Skriv därför undantagssäker kod när du anropar runtime eller anropar ett annat bibliotek som anropar runtime.
Mönstret Resource Acquisition Is Initialization (RAII) är ett sätt att på ett säkert sätt hantera livslängden för ett samtidighetsobjekt under ett visst omfång. Under RAII-mönstret allokeras en datastruktur på stacken. Datastrukturen initierar eller hämtar en resurs när den skapas och förstör eller frigör resursen när datastrukturen förstörs. RAII-mönstret garanterar att destruktorn anropas innan det omgivande omfånget avslutas. Det här mönstret är användbart när en funktion innehåller flera return instruktioner. Det här mönstret hjälper dig också att skriva undantagssäker kod. När en throw -instruktion får stacken att varva ned anropas destruktor för RAII-objektet. Därför tas resursen alltid bort eller frigörs korrekt.
Körtiden definierar flera klasser som använder RAII-mönstret, till exempel konkurrens::critical_section::scoped_lock och konkurrens::reader_writer_lock::scoped_lock. Dessa hjälpklasser kallas avgränsade lås. Dessa klasser ger flera fördelar när du arbetar med concurrency::critical_section eller concurrency::reader_writer_lock objekt. Konstruktorn för dessa klasser ger åtkomst till det angivna critical_section-objektet eller reader_writer_lock-objektet; destruktören frigör åtkomsten till det objektet. Eftersom ett begränsat lås frigör åtkomst till dess objekt för ömsesidig uteslutning automatiskt när det förstörs låser du inte upp det underliggande objektet manuellt.
Tänk på följande klass, account, som definieras av ett externt bibliotek och därför inte kan ändras.
// 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;
};
I följande exempel utförs flera transaktioner på ett account objekt parallellt. I exemplet används ett critical_section objekt för att synkronisera åtkomsten account till objektet eftersom account klassen inte är samtidighetssäker. Varje parallell åtgärd använder ett critical_section::scoped_lock objekt för att garantera att critical_section objektet låss upp när åtgärden antingen lyckas eller misslyckas. När kontosaldot är negativt misslyckas åtgärden withdraw genom att utlösa ett undantag.
// 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;
}
}
I det här exemplet skapas följande exempelutdata:
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
Ytterligare exempel som använder RAII-mönstret för att hantera livslängden för samtidighetsobjekt finns i Genomgång: Ta bort arbete från en User-Interface tråd, Hur man använder kontextklassen för att implementera en kooperativ semafor och Hur man använder överprenumeration för att kompensera för fördröjning.
[Topp]
Skapa inte samtidighetsobjekt i globalt omfång
När du skapar ett samtidighetsobjekt i det globala omfånget kan du orsaka problem som dödläge eller minnesåtkomstöverträdelser i ditt program.
När du till exempel skapar ett Concurrency Runtime-objekt skapas en standardschemaläggare för dig av runtime om en inte har skapats ännu. Ett exekveringsobjekt som skapas under den globala objektkonstruktionen kommer att orsaka att exekveringsmiljön skapar denna standardschemaläggare. Den här processen tar dock ett internt lås som kan störa initieringen av andra objekt som stöder Concurrency Runtime-infrastrukturen. Det här interna låset kan krävas av ett annat infrastrukturobjekt som ännu inte har initierats och som därmed kan orsaka ett dödläge i ditt program.
Det här exemplet demonstrerar skapandet av ett globalt concurrency::Scheduler-objekt. Det här mönstret gäller inte bara för Scheduler klassen utan alla andra typer som tillhandahålls av Concurrency Runtime. Vi rekommenderar att du inte följer det här mönstret eftersom det kan orsaka oväntat beteende i ditt program.
// 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()
{
}
Exempel på rätt sätt att skapa Scheduler objekt finns i Schemaläggaren.
[Topp]
Använd inte samtidighetsobjekt i delade datasegment
Concurrency Runtime stöder inte användning av samtidighetsobjekt i ett delat dataavsnitt, till exempel ett dataavsnitt som skapas av data_seg-direktivet#pragma . Ett samtidighetsobjekt som delas över processgränser kan försätta körmiljön i ett inkonsistent eller ogiltigt tillstånd.
[Topp]
Se även
Bästa praxis för samtidighetskörning
PPL (Parallel Patterns Library)
Asynkront agentbibliotek
Aktivitetsschemaläggare
Datastrukturer för synkronisering
Jämföra synkroniseringsdatastrukturer med Windows-API:et
Så här gör du: Använd Alloc och Free för att förbättra minnesprestanda
Gör så här: Använd överprenumeration för att förskjuta svarstid
Gör så här: Använda kontextklassen för att implementera en kooperativ semafor
Genomgång: Ta bort arbete från en User-Interface tråd
Bästa praxis i Biblioteket för parallella mönster
Metodtips i biblioteket för asynkrona agenter