Parallel.ForEach, etkin iş parçacığı sayısını sınırlıyor mu?


107

Bu kod verildiğinde:

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

1000 parçacığın tümü neredeyse aynı anda mı ortaya çıkacak?

Yanıtlar:


149

Hayır, 1000 iş parçacığı başlatmayacak - evet, kaç iş parçacığı kullanılacağını sınırlayacaktır. Paralel Uzantılar, fiziksel olarak kaç tane bulunduğunuza ve kaç tanesinin halihazırda meşgul olduğuna bağlı olarak uygun sayıda çekirdek kullanır . İşi her çekirdek için ayırır ve ardından her iş parçacığının kendi kuyruğunu verimli bir şekilde işlemesine izin vermek için iş çalma adı verilen bir teknik kullanır ve yalnızca gerçekten ihtiyaç duyulduğunda pahalı bir çapraz iş parçacığı erişimi yapması gerekir.

Göz at PFX Takım Blog için yükler o işi nasıl tahsis hakkında bilgi ve diğer konular her türlü.

Bazı durumlarda istediğiniz paralellik derecesini de belirtebileceğinizi unutmayın.


2
Parallel.ForEach (FilePathArray, path => ... kullanarak bu gece yaklaşık 24.000 dosya okumak için, okuduğum her dosya için yeni bir dosya oluşturuyordum. Çok basit bir kod. 7200 RPM diskini aşmak için 6 iş parçacığı bile yeterliydi % 100 kullanımda okuyordum. Birkaç saat boyunca Parallel kitaplığının 8.000'den fazla iş parçacığı dönüşünü izledim. MaxDegreeOfParallelism kullanarak test ettim ve 8000'den fazla iş parçacığının kaybolduğundan emin oldum. Şimdi aynı şekilde birçok kez test ettim sonuç.
Jake Drew

Bu olabilir bazı dejenere 'DoSomething' 1000 konuları başlar. (Şu anda üretim kodunda bir sınır belirleyemeyen ve 200'den fazla iş parçacığı oluşturan ve dolayısıyla SQL bağlantı havuzunu açan bir sorunla uğraştığım durumda olduğu gibi .. Önemsiz bir şekilde gerekçelendirilemeyen herhangi bir çalışma için Max DOP'u ayarlamanızı öneririm. yaklaşık olarak açıkça CPU'ya bağlı.)
user2864740


28

Tek çekirdekli bir makinede ... Parallel.ForEach bölümleri (yığınları) üzerinde çalıştığı koleksiyonun bir dizi iş parçacığı arasında çalıştığı, ancak bu sayı hesaba katan ve tarafından yapılan işi sürekli izleyen bir algoritmaya göre hesaplanır. iş parçacıkları ForEach'e tahsis ediyor. Dolayısıyla , ForEach'in vücut kısmı, iş parçacığını beklemede bırakacak uzun süre çalışan IO-bağlı / engelleme işlevlerine çağrı yaparsa, algoritma daha fazla iş parçacığı oluşturur ve koleksiyonu bunlar arasında yeniden bölümlendirir . İş parçacıkları hızlı bir şekilde tamamlanırsa ve örneğin yalnızca bazı sayıları hesaplamak gibi GÇ iş parçacıklarını engellemezse,algoritma, iş parçacığı sayısını, algoritmanın verim için optimum (her yinelemenin ortalama tamamlanma süresi) olarak değerlendirdiği bir noktaya yükseltir (veya gerçekten düşürür) .

Temel olarak, çeşitli Paralel kitaplık işlevlerinin arkasındaki iş parçacığı havuzu, kullanılacak optimum sayıda iş parçacığı belirleyecektir. Fiziksel işlemci çekirdeklerinin sayısı denklemin yalnızca bir bölümünü oluşturur. Çekirdek sayısı ile ortaya çıkan iş parçacığı sayısı arasında basit bir bire bir ilişki YOKTUR.

İş parçacığı senkronizasyonunun iptali ve işlenmesi hakkındaki belgeleri çok yararlı bulmuyorum. Umarım MS, MSDN'de daha iyi örnekler sağlayabilir.

Unutmayın, gövde kodu birden fazla iş parçacığı üzerinde çalışacak şekilde yazılmalıdır, tüm olağan iş parçacığı güvenliği hususları ile birlikte çerçeve bu faktörü henüz soyutlamaz.


1
"..ForEach'in vücut kısmı, iş parçacığını beklemede bırakacak uzun çalışan engelleme işlevlerine çağrı yaparsa, algoritma daha fazla iş parçacığı oluşturacaktır." - Bozulmuş durumlarda bu, izin verildiği kadar çok iş parçacığı oluşturulabileceği anlamına gelir. ThreadPool başına.
user2864740

2
Haklısın, IO için kendimde hata
ayıkladığımda

5

İşlemci / çekirdek sayısına bağlı olarak optimum sayıda iş parçacığı oluşturur. Hepsi aynı anda ortaya çıkmayacak.



4

Harika soru. Örneğinizde, dört çekirdekli bir işlemcide bile paralelleştirme düzeyi oldukça düşüktür, ancak biraz beklemekle paralelleştirme düzeyi oldukça yükselebilir.

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

Şimdi bir HTTP isteğini simüle etmek için bir bekleme işlemi eklendiğinde ne olduğuna bakın.

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

Henüz herhangi bir değişiklik yapmadım ve eşzamanlılık / paralellik düzeyi dramatik bir şekilde sıçradı. Eşzamanlılık limiti ile artırılabilir 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());
}

Ayarı tavsiye ederim ParallelOptions.MaxDegreeOfParallelism. Bu, kullanımdaki iş parçacığı sayısını artırmayacaktır, ancak yalnızca mantıklı sayıda iş parçacığı başlatmanızı sağlayacaktır, bu da sizin endişeniz gibi görünüyor.

Son olarak sorunuzu yanıtlamak için, hayır, tüm ileti dizilerinin aynı anda başlamasını sağlayamazsınız. Parallel.Invoke'u, mükemmel şekilde paralel olarak çağırmak istiyorsanız, örneğin yarış koşullarını test etmek istiyorsanız kullanın.

// 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)));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.