Parallel.ForEach limita il numero di thread attivi?


107

Dato questo codice:

var arrayStrings = new string[1000];
Parallel.ForEach<string>(arrayStrings, someString =>
{
    DoSomething(someString);
});

Tutti i 1000 thread verranno generati quasi contemporaneamente?

Risposte:


149

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.


2
Stavo usando Parallel.ForEach (FilePathArray, path => ... per leggere circa 24.000 file stasera creando un nuovo file per ogni file che ho letto. Codice molto semplice. Sembra che anche 6 thread siano stati sufficienti per sopraffare il disco da 7200 RPM Stavo leggendo da al 100% di utilizzo. Nel giro di poche ore ho visto la libreria Parallel girare oltre 8.000 thread. Ho provato usando MaxDegreeOfParallelism e abbastanza sicuro che gli oltre 8000 thread sono scomparsi. L'ho testato più volte ora con lo stesso risultato
Jake Drew

Si potrebbe iniziare a 1000 discussioni per un po 'degenerati 'DoSomething'. (Come nel caso in cui attualmente sto affrontando un problema nel codice di produzione che non è riuscito a impostare un limite e ha generato più di 200 thread, facendo scoppiare così il pool di connessioni SQL .. Consiglio di impostare il Max DOP per qualsiasi lavoro che non può essere banalmente ragionato circa come esplicitamente vincolato alla CPU.)
user2864740


28

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.


1
"..se la parte del corpo di ForEach richiama funzioni di blocco a esecuzione prolungata che lascerebbero il thread in attesa, l'algoritmo genererà più thread .." - In casi degenerati questo significa che potrebbero esserci tanti thread creati quanti sono consentiti per ThreadPool.
user2864740

2
Hai ragione, per IO potrebbe allocare +100 thread mentre ho eseguito il debug da solo
FindOutIslamNow

5

Calcola un numero ottimale di thread in base al numero di processori / core. Non verranno generati tutti in una volta.



4

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)));
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.