web-dev-qa-db-fra.com

Parallel.ForEach limite-t-il le nombre de threads actifs?

Compte tenu de ce code:

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

Est-ce que les 1000 threads apparaîtront presque simultanément?

99
Jader Dias

Non, cela ne démarrera pas 1000 threads - oui, cela limitera le nombre de threads utilisés. Les extensions parallèles utilisent un nombre approprié de cœurs, en fonction du nombre que vous avez physiquement et combien sont déjà occupés. Il alloue du travail pour chaque cœur et utilise ensuite une technique appelée vol de travail pour permettre à chaque thread de traiter efficacement sa propre file d'attente et de ne faire que des croisements coûteux. accès aux threads quand il en a vraiment besoin.

Jetez un œil au PFX Team Blog pour charges d'informations sur la façon dont il répartit le travail et toutes sortes d'autres sujets.

Notez que dans certains cas, vous pouvez également spécifier le degré de parallélisme souhaité.

140
Jon Skeet

Sur une seule machine de base ... Parallel.ForEach partitions (morceaux) de la collection sur laquelle il travaille entre un certain nombre de threads, mais ce nombre est calculé sur la base d'un algorithme qui prend en compte et semble surveiller en permanence le travail effectué par le threads qu'il alloue à ForEach. Donc si la partie corps de ForEach appelle à des fonctions de blocage/liaison IO de longue durée qui laisseraient le thread en attente, l'algorithme générera plus de threads et répartira la collection entre eux. Si les threads se terminent rapidement et ne se bloquent pas sur les IO threads par exemple, comme simplement calculer certains nombres, l'algorithme augmentera (ou même diminuera) le nombre de threads) à un point où l'algorithme considère optimal pour le débit (temps moyen d'achèvement de chaque itération).

Fondamentalement, le pool de threads derrière toutes les différentes fonctions de la bibliothèque parallèle, déterminera un nombre optimal de threads à utiliser. Le nombre de cœurs de processeur physiques ne représente qu'une partie de l'équation. Il n'y a PAS de relation un à un simple entre le nombre de cœurs et le nombre de threads générés.

Je ne trouve pas la documentation sur l'annulation et la gestion des threads de synchronisation très utile. Espérons que MS puisse fournir de meilleurs exemples dans MSDN.

N'oubliez pas, le code du corps doit être écrit pour fonctionner sur plusieurs threads, avec toutes les considérations habituelles de sécurité des threads, le cadre n'abstrait pas encore ce facteur ... pour le moment.

26
Microsoft Developer

Voir Parallel.For utilise-t-il une tâche par itération? pour une idée d'un "modèle mental" à utiliser. Cependant, l'auteur déclare qu '"en fin de compte, il est important de se rappeler que les détails de la mise en œuvre peuvent changer à tout moment".

5
Kevin Hakanson

Il établit un nombre optimal de threads en fonction du nombre de processeurs/cœurs. Ils n'apparaîtront pas tous en même temps.

5
Colin Mackay

Grande question. Dans votre exemple, le niveau de parallélisation est assez bas même sur un processeur quad core, mais avec certains en attente, le niveau de parallélisation peut devenir assez élevé.

// 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());
}

Regardez maintenant ce qui se passe lorsqu'une opération en attente est ajoutée pour simuler une requête 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());
}

Je n'ai pas encore apporté de modifications et le niveau de concurrence/parallélisation a considérablement augmenté. La concurrence peut voir sa limite augmentée avec 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());
}

Je recommande le réglage ParallelOptions.MaxDegreeOfParallelism. Cela n'augmentera pas nécessairement le nombre de threads utilisés, mais vous garantira de ne démarrer qu'un nombre raisonnable de threads, ce qui semble être votre préoccupation.

Enfin, pour répondre à votre question, non, vous n'obtiendrez pas toutes les discussions pour démarrer en même temps. Utilisez Parallel.Invoke si vous cherchez à invoquer parfaitement en parallèle, par ex. tester les conditions de course.

// 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)));
}
3
Timothy Gonzalez