Dato questo codice:
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
Tutti i 1000 thread verranno generati quasi contemporaneamente?
Dato questo codice:
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
Tutti i 1000 thread verranno generati quasi contemporaneamente?
Risposte:
No, non avvierà 1000 thread - sì, limiterà il numero di thread utilizzati. Parallel Extensions utilizza un numero appropriato di core, in base a quanti ne hai fisicamente e quanti sono già occupati. Alloca il lavoro per ogni core e quindi utilizza una tecnica chiamata furto del lavoro per consentire a ogni thread di elaborare la propria coda in modo efficiente e deve solo eseguire qualsiasi costoso accesso cross-thread quando è realmente necessario.
Dai un'occhiata al blog del team PFX per un sacco di informazioni su come alloca il lavoro e tutti i tipi di altri argomenti.
Nota che in alcuni casi puoi anche specificare il grado di parallelismo che desideri.
Su una macchina single core ... Parallel.For Ogni partizione (blocchi) della raccolta su cui sta lavorando tra un numero di thread, ma quel numero viene calcolato in base a un algoritmo che tiene conto e sembra monitorare continuamente il lavoro svolto dal thread viene allocato a ForEach. Quindi, se la parte del corpo del ForEach richiama funzioni di blocco / vincolo IO a lunga esecuzione che lascerebbero il thread in attesa, l'algoritmo genererà più thread e ripartizionerà la raccolta tra di loro . Se i thread si completano rapidamente e non si bloccano sui thread di I / O, ad esempio semplicemente calcolando alcuni numeri,l'algoritmo aumenterà (o addirittura diminuirà) il numero di thread fino a un punto in cui l'algoritmo considera ottimale per il throughput (tempo medio di completamento di ogni iterazione) .
Fondamentalmente il pool di thread dietro tutte le varie funzioni della libreria parallela, elaborerà un numero ottimale di thread da utilizzare. Il numero di core del processore fisico costituisce solo una parte dell'equazione. NON esiste una semplice relazione uno a uno tra il numero di core e il numero di thread generati.
Non trovo molto utile la documentazione sull'annullamento e la gestione della sincronizzazione dei thread. Si spera che MS possa fornire esempi migliori in MSDN.
Non dimenticare, il codice del corpo deve essere scritto per essere eseguito su più thread, insieme a tutte le consuete considerazioni sulla sicurezza dei thread, il framework non astrae quel fattore ... ancora.
Calcola un numero ottimale di thread in base al numero di processori / core. Non verranno generati tutti in una volta.
Vedi Does Parallel.For use one Task per iteration? per un'idea di un "modello mentale" da utilizzare. Tuttavia, l'autore afferma che "Alla fine della giornata, è importante ricordare che i dettagli di implementazione possono cambiare in qualsiasi momento".
Ottima domanda. Nel tuo esempio, il livello di parallelizzazione è piuttosto basso anche su un processore quad core, ma con qualche attesa il livello di parallelizzazione può diventare piuttosto alto.
// Max concurrency: 5
[Test]
public void Memory_Operations()
{
ConcurrentBag<int> monitor = new ConcurrentBag<int>();
ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
monitor.Add(monitor.Count);
monitor.TryTake(out int result);
monitorOut.Add(result);
});
Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}
Ora guarda cosa succede quando viene aggiunta un'operazione in attesa per simulare una richiesta HTTP.
// Max concurrency: 34
[Test]
public void Waiting_Operations()
{
ConcurrentBag<int> monitor = new ConcurrentBag<int>();
ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
monitor.Add(monitor.Count);
System.Threading.Thread.Sleep(1000);
monitor.TryTake(out int result);
monitorOut.Add(result);
});
Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}
Non ho ancora apportato modifiche e il livello di concorrenza / parallelizzazione è aumentato notevolmente. È possibile aumentare il limite della concorrenza con ParallelOptions.MaxDegreeOfParallelism
.
// Max concurrency: 43
[Test]
public void Test()
{
ConcurrentBag<int> monitor = new ConcurrentBag<int>();
ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
var arrayStrings = new string[1000];
var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
Parallel.ForEach<string>(arrayStrings, options, someString =>
{
monitor.Add(monitor.Count);
System.Threading.Thread.Sleep(1000);
monitor.TryTake(out int result);
monitorOut.Add(result);
});
Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}
// Max concurrency: 391
[Test]
public void Test()
{
ConcurrentBag<int> monitor = new ConcurrentBag<int>();
ConcurrentBag<int> monitorOut = new ConcurrentBag<int>();
var arrayStrings = new string[1000];
var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
Parallel.ForEach<string>(arrayStrings, options, someString =>
{
monitor.Add(monitor.Count);
System.Threading.Thread.Sleep(100000);
monitor.TryTake(out int result);
monitorOut.Add(result);
});
Console.WriteLine("Max concurrency: " + monitorOut.OrderByDescending(x => x).First());
}
Consiglio l'impostazione ParallelOptions.MaxDegreeOfParallelism
. Non aumenterà necessariamente il numero di thread in uso, ma ti assicurerà di avviare solo un numero sano di thread, che sembra essere la tua preoccupazione.
Infine, per rispondere alla tua domanda, no, non inizierai tutti i thread contemporaneamente. Usa Parallel.Invoke se stai cercando di invocare perfettamente in parallelo, ad esempio testando le condizioni di gara.
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623363344
// 636462943623368346
// 636462943623368346
// 636462943623373351
// 636462943623393364
// 636462943623393364
[Test]
public void Test()
{
ConcurrentBag<string> monitor = new ConcurrentBag<string>();
ConcurrentBag<string> monitorOut = new ConcurrentBag<string>();
var arrayStrings = new string[1000];
var options = new ParallelOptions {MaxDegreeOfParallelism = int.MaxValue};
Parallel.ForEach<string>(arrayStrings, options, someString =>
{
monitor.Add(DateTime.UtcNow.Ticks.ToString());
monitor.TryTake(out string result);
monitorOut.Add(result);
});
var startTimes = monitorOut.OrderBy(x => x.ToString()).ToList();
Console.WriteLine(string.Join(Environment.NewLine, startTimes.Take(10)));
}