Veri ve Görev Paralelliğinde Olası Tuzaklar

Çoğu durumda ve, Parallel.For Parallel.ForEach sıradan sıralı döngülere göre önemli performans geliştirmeleri sağlar. Ancak, döngüyü paralelleştirme çalışması, sıralı kodda o kadar yaygın olmayan veya hiç karşılaşmamış sorunlara yol açan karmaşıklık getirir. Bu konuda, paralel döngüler yazmaktan kaçınılması gereken bazı uygulamalar listelemektedir.

Paralelin her zaman daha hızlı olduğunu varsayma

Bazı durumlarda paralel döngü, sıralı eşdeğerden daha yavaş çalışır. Temel kural, birkaç yineleme ve hızlı kullanıcı temsilcisine sahip paralel döngülerin çok fazla hızlama ihtimalinin düşük olduğu kuraldır. Ancak, performansa birçok faktör dahil olduğundan, her zaman gerçek sonuçları ölçmenizi öneririz.

Paylaşılan Bellek Konumlara Yazmaktan Kaçının

Sıralı kodda, statik değişkenlerden veya sınıf alanlarından okuma veya yazma yaygın değildir. Ancak, birden çok iş parçacığı bu tür değişkenlere eşzamanlı olarak erişirken yarış koşulları için büyük bir potansiyel vardır. Değişkene erişimi eşitlemek için kilitleri kullanabilirsiniz ancak eşitleme maliyeti performansı etkileyebilir. Bu nedenle, mümkün olduğunca paralel bir döngüde paylaşılan durumlara erişimi önlemenizi veya en azından sınırlamayı öneririz. Bunu yapmak için en iyi yol, döngü yürütme sırasında iş parçacığı yerel durumunu depolamak için bir değişken kullanan ve aşırı Parallel.For Parallel.ForEach System.Threading.ThreadLocal<T> yüklemelerini kullanmaktır. Daha fazla bilgi için, bkz. How to: Write a Parallel.For Loop with Thread-Local Variables ve How to: Write a Parallel.ForEach Loop with Partition-Local Variables.

Bu Over-Parallelization

Paralel döngüler kullanarak, kaynak koleksiyonu bölümleme ve çalışan iş parçacıklarını eşitlemenin ek maliyetlerine neden olur. Paralelleştirmenin avantajları, bilgisayarda işlemci sayısıyla da sınırlıdır. Yalnızca bir işlemcide birden çok işleme bağlı iş parçacığı çalıştırarak bir hız kazanılam yoktur. Bu nedenle, bir döngüyü aşırı paralelleştirmeye özenli olmalısınız.

Aşırı paralelleştirmenin ortaya çıkar olduğu en yaygın senaryo, iç içe geçmiş döngülerdedir. Çoğu durumda, aşağıdaki koşullardan biri veya daha fazlası geçerli olmadığı sürece yalnızca dış döngüyü paralelleştirmek en iyisidir:

  • İç döngü çok uzun olduğu bilinir.

  • Her siparişte pahalı bir hesaplama gerçekleştirin. (Örnekte gösterilen işlem pahalı değildir.)

  • Hedef sistemin üzerinde sorgu paralelleştirerek üretecek iş parçacığı sayısını işlemek için yeterli işlemcilere sahip olduğu cust.Orders bilinir.

Her durumda en uygun sorgu şeklini belirlemenin en iyi yolu test etmek ve ölçmektir.

İş Parçacığı Olmayan Kasa Çağrıları Önleme

Paralel döngüden iş parçacığı güvenli olmayan örnek yöntemlerine yazmak, programda algılanmayacak veya algılanmayacak veri bozulmasına yol açabilirsiniz. Ayrıca özel durumlara yol da olabilir. Aşağıdaki örnekte, birden çok iş parçacığı aynı anda yöntemini çağırmaya çalışır FileStream.WriteByte ve bu sınıf tarafından desteklenmiyor.

FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));
Dim fs As FileStream = File.OpenWrite(filepath)
Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))

Çağrıları Thread-Safe Yöntemleriyle Sınırlama

.NET'te çoğu statik yöntem iş parçacığı için güvenlidir ve eşzamanlı olarak birden çok iş parçacığından çağrılabilirsiniz. Ancak, bu durumlarda bile, söz konusu eşitleme sorguda önemli ölçüde yavaşlamaya yol açabilirsiniz.

Not

Sorgularınıza bazı çağrılar ekerek bunu kendiniz WriteLine sınayabilirsiniz. Bu yöntem belge örneklerinde gösterim amacıyla kullanılmış olsa da, gerekli olmadıkça paralel döngülerde bunu kullanma.

İş Parçacığı Benzeşliği Sorunlarına Dikkat

Single-Threaded Forms, Windows Forms ve Windows Presentation Foundation (WPF) için COM birlikte çalışabilirliği gibi bazı teknolojiler, kodun belirli bir iş parçacığında çalışması gereken iş parçacığı benzeşliği kısıtlamaları oluşturur. Örneğin hem Formlar hem Windows WPF'de denetime yalnızca üzerinde oluşturulan iş parçacığında erişilebilir. Bu, örneğin iş parçacığı zamanlayıcıyı yalnızca kullanıcı arabirimi iş parçacığında iş zamanlaması için yapılandırmadıkça paralel bir döngüden liste denetimi güncelleştire anlamına gelir. Daha fazla bilgi için bkz. Eşitleme bağlamı belirtme.

Parallel.Invoke Tarafından Çağrılan Temsilcilerde Beklerken Dikkatli Olun

Bazı durumlarda, Görev Paralel Kitaplığı bir görevi satır içi olarak alar; başka bir anlama gelir; bu, o anda yürütülen iş parçacığında görev üzerinde çalışır. (Daha fazla bilgi için bkz. Görev Zamanlayıcıları.) Bu performans iyileştirmesi bazı durumlarda kilitlenmeye neden olabilir. Örneğin, iki görev aynı temsilci kodunu çalıştırabilir ve bu kod bir olay oluştuğunda sinyal verir ve ardından diğer görevin sinyal oluşturması için bekler. İkinci görev, birinciyle aynı iş parçacığında yer alan ve ilk görev Bekleme durumuna girdiyse, ikinci görev hiçbir zaman olayına sinyal gönderemiyor. Böyle bir durumun oluşmasını önlemek için Bekleme işlemi üzerinde bir zaman aşımı belirtebilirsiniz veya bir görevin diğer görevi engelleyenemlerinden emin olmak için açık iş parçacığı oluşturucuları kullanabilirsiniz.

ForEach, For ve ForAll Yinelemelerinin Her Zaman Paralel Yürütülür olduğunu Varsayma

Bir veya döngüsünde tek tek yinelemelerin paralel olarak yürütül gerektire göstere göstere olduğunu For ForEach ForAll unutmayın. Bu nedenle, yinelemelerin paralel yürütülmesine veya yinelemelerin belirli bir sırayla yürütülmesine bağlı olan herhangi bir kodu yazmaktan kaçınmanız gerekir. Örneğin, bu kod büyük olasılıkla kilitlenmeye neden olur:

ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100)
    .AsParallel()
    .ForAll((j) =>
        {
            if (j == Environment.ProcessorCount)
            {
                Console.WriteLine("Set on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Set();
            }
            else
            {
                Console.WriteLine("Waiting on {0} with value of {1}",
                    Thread.CurrentThread.ManagedThreadId, j);
                mre.Wait();
            }
        }); //deadlocks
Dim mres = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)

            If j = Environment.ProcessorCount Then
                Console.WriteLine("Set on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Set()
            Else
                Console.WriteLine("Waiting on {0} with value of {1}",
                                  Thread.CurrentThread.ManagedThreadId, j)
                mres.Wait()
            End If
        End Sub) ' deadlocks

Bu örnekte, bir yineleme bir olay ayarlar ve diğer tüm yinelemeler olayda bekler. Olay ayarı yinelemesi tamamlanana kadar bekleyen yinelemelerin hiçbiri tamamlanamadı. Ancak, olay ayarı yinelemesi yürütme şansına sahip olmadan önce, bekleyen yinelemeler paralel döngüyü yürütmek için kullanılan tüm iş parçacıklarını engelleyebilir. Bu bir kilitlenmeye neden olur; olay ayarı yinelemesi hiçbir zaman yürütülmez ve bekleyen yinelemeler hiçbir zaman uyandırmaz.

Özellikle, bir paralel döngü yinelemesi hiçbir zaman ilerlemeyi yapmak için döngüdeki başka bir yinelemeyi beklemez. Paralel döngü yinelemeleri sırayla ancak ters sırada zamanlamayı belirlerse bir kilitlenme oluşur.

Ui İş Parçacığında Paralel Döngüleri Yürütmekten Kaçınma

Uygulamanın kullanıcı arabirimini (UI) yanıtlamak önemlidir. Bir işlem paralelleştirmeyi garanti etmek için yeterli iş içeriyorsa, büyük olasılıkla bu işlem kullanıcı arabirimi iş parçacığında çalıştırılamamalı. Bunun yerine, bir arka plan iş parçacığında çalıştırmak için bu işlemi boşaltması gerekir. Örneğin, bir kullanıcı arabirimi denetiminde işlenecek bazı verileri hesaplamak için paralel bir döngü kullanmak için, döngüyü doğrudan bir UI olay işleyicisi yerine bir görev örneği içinde yürütmeyi göz önünde bulundurabilirsiniz. Yalnızca çekirdek hesaplama tamamlandığında kullanıcı arabirimi güncelleştirmesini ui iş parçacığına geri sıralamalısiniz.

Ui iş parçacığında paralel döngüler çalıştırıyorsanız, döngü içinde kullanıcı arabirimi denetimlerini güncelleştirmekten kaçınmaya dikkat edin. UI iş parçacığında yürütülen paralel bir döngüden UI denetimlerini güncelleştirme girişimi, ui güncelleştirmenin nasıl çağrıldığında bağlı olarak durum bozulmasına, özel durumlara, gecikmeli güncelleştirmelere ve hatta kilitlenmelere yol açabilirsiniz. Aşağıdaki örnekte, paralel döngü tüm yinelemeler tamamlandıktan sonra yürütülebilir kullanıcı arabirimi iş parçacığını engeller. Ancak, döngü yinelemesi bir arka plan iş parçacığında (örneğin) çalışıyorsa, Invoke çağrısı ui iş parçacığına bir ileti gönderiliyor ve bu iletinin işlenmesini bekleyen blokları For neden oluyor. kullanıcı arabirimi iş parçacığının çalıştırması engellendiğinden, ileti hiçbir zaman For işlenemez ve UI iş parçacığı kilitlenmeleri.

private void button1_Click(object sender, EventArgs e)
{
    Parallel.For(0, N, i =>
    {
        // do work for i
        button1.Invoke((Action)delegate { DisplayProgress(i); });
    });
}
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Parallel.For(0, iterations, Sub(x)
                                    Button1.Invoke(Sub()
                                                       DisplayProgress(x)
                                                   End Sub)
                                End Sub)
End Sub

Aşağıdaki örnek, döngüyü bir görev örneğinde çalıştırarak kilitlenmeyi önlemeyi gösterir. Ui iş parçacığı döngü tarafından engellenmez ve ileti işlenebilir.

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
        Parallel.For(0, N, i =>
        {
            // do work for i
            button1.Invoke((Action)delegate { DisplayProgress(i); });
        })
         );
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim iterations As Integer = 20
    Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
                                                                Button1.Invoke(Sub()
                                                                                   DisplayProgress(x)
                                                                               End Sub)
                                                            End Sub))
End Sub

Ayrıca bkz.