Yönetilen iş parçacığı oluşturma en iyi yöntemleri

Çoklu iş parçacığı kullanımı dikkatli programlama gerektirir. Çoğu görev için, iş parçacığı havuzu iş parçacıkları tarafından yürütülmeye yönelik istekleri kuyruğa alarak karmaşıklığı azaltabilirsiniz. Bu konu başlığında, birden çok iş parçacığının çalışmasını koordine etme veya engelleyen iş parçacıklarını işleme gibi daha zor durumlar ele alınmıştır.

Not

.NET Framework 4'den başlayarak, Görev Paralel Kitaplığı ve PLINQ, çok iş parçacıklı programlamanın karmaşıklığını ve risklerini azaltan API'ler sağlar. Daha fazla bilgi için bkz . .NET'te Paralel Programlama.

Kilitlenmeler ve yarış koşulları

Çoklu iş parçacığı kullanımı aktarım hızı ve yanıt verme hızıyla ilgili sorunları çözer, ancak bunu yaparken yeni sorunlar getirir: kilitlenmeler ve yarış koşulları.

Kilitlenmeler

İki iş parçacığının her biri diğerinin zaten kilitlediği bir kaynağı kilitlemeye çalıştığında kilitlenme oluşur. hiçbir iş parçacığı daha fazla ilerleme kaydedemez.

Yönetilen iş parçacığı oluşturma sınıflarının birçok yöntemi, kilitlenmeleri algılamanıza yardımcı olmak için zaman aşımları sağlar. Örneğin, aşağıdaki kod adlı lockObjectbir nesnede kilit almaya çalışır. Kilit 300 milisaniye içinde alınmazsa döndürür Monitor.TryEnterfalse.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

Yarış koşulları

Yarış durumu, bir programın sonucu, iki veya daha fazla iş parçacığından hangisinin önce belirli bir kod bloğuna ulaştığına bağlı olduğunda oluşan bir hatadır. Programı birçok kez çalıştırmak farklı sonuçlar üretir ve belirli bir çalıştırmanın sonucu tahmin edilemez.

Yarış durumu için basit bir örnek, bir alanı artırmaktır. Bir sınıfın, (C#) veya objCt += 1 (Visual Basic) gibi objCt++; bir kod kullanılarak, sınıfın her örneği oluşturulduğunda artırılan bir özel statik alanı (Visual Basic'te paylaşılan) olduğunu varsayalım. Bu işlem, değerini bir objCt yazmaç içine yüklemeyi, değeri artırmayı ve içinde objCtdepolamayı gerektirir.

Çok iş parçacıklı bir uygulamada, değeri yükleyip artıran bir iş parçacığı, üç adımın tümünü gerçekleştiren başka bir iş parçacığı tarafından önceden yüklenmiş olabilir; İlk iş parçacığı yürütmeye devam ettiğinde ve değerini depoladığında, değerin geçici olarak değiştiği gerçeğini hesaba katmadan üzerine yazar objCt .

Bu belirli yarış durumu, gibi Interlocked.Incrementsınıfının yöntemleri Interlocked kullanılarak kolayca önlenir. Verileri birden çok iş parçacığı arasında eşitlemeye yönelik diğer teknikler hakkında bilgi edinmek için bkz . Multithreading için Verileri Eşitleme.

Yarış koşulları, birden çok iş parçacığının etkinliklerini eşitlediğinizde de oluşabilir. Bir kod satırı yazdığınızda, bir iş parçacığı satırı yürütmeden önce (veya satırı oluşturan tek tek makine yönergelerinden önce) ve başka bir iş parçacığı tarafından ele geçirilirse neler olabileceğini göz önünde bulundurmanız gerekir.

Statik üyeler ve statik oluşturucular

Sınıf oluşturucu (static Visual Basic'te C# Shared Sub New içindeki oluşturucu) çalışmasını tamamlayana kadar sınıf başlatılmaz. Başlatılmamış bir türdeki kodun yürütülmesini önlemek için ortak dil çalışma zamanı, sınıf oluşturucusunun çalışması tamamlanana kadar diğer iş parçacıklarından sınıfın üyelerine static (Shared Visual Basic'teki üyeler) yapılan tüm çağrıları engeller.

Örneğin, bir sınıf oluşturucu yeni bir iş parçacığı başlatırsa ve iş parçacığı yordamı sınıfın bir static üyesini çağırırsa, sınıf oluşturucu tamamlanana kadar yeni iş parçacığı blokları.

Bu, oluşturucuya sahip static olabilecek her tür için geçerlidir.

İşlemci sayısı

Bir sistemde birden çok işlemci veya yalnızca bir işlemci olup olmadığı çok iş parçacıklı mimariyi etkileyebilir. Daha fazla bilgi için bkz . İşlemci Sayısı.

Environment.ProcessorCount Çalışma zamanında kullanılabilen işlemci sayısını belirlemek için özelliğini kullanın.

Genel öneriler

Birden çok iş parçacığı kullanırken aşağıdaki yönergeleri göz önünde bulundurun:

  • Diğer iş parçacıklarını sonlandırmak için kullanmayın Thread.Abort . Başka bir iş parçacığında çağrı Abort yapmak, iş parçacığının işlenmesinde hangi noktaya ulaştığını bilmeden bu iş parçacığında bir özel durum oluşturmakla benzerdir.

  • birden çok iş parçacığının etkinliklerini eşitlemek için ve Thread.Resume kullanmayınThread.Suspend. , , ManualResetEventAutoResetEventve MonitorkullanınMutex.

  • Ana programınızdan çalışan iş parçacıklarının yürütülmesini denetlemeyin (örneğin, olayları kullanarak). Bunun yerine, çalışma kullanılabilir olana kadar çalışan iş parçacıklarının beklemesi, yürütmesi ve bittiğinde programınızın diğer bölümlerine bildirilmesinden sorumlu olacak şekilde programınızı tasarlayın. Çalışan iş parçacıklarınız engellenmiyorsa, iş parçacığı havuzu iş parçacıklarını kullanmayı göz önünde bulundurun. Monitor.PulseAll , çalışan iş parçacıklarının engellendiği durumlarda kullanışlıdır.

  • Türleri kilit nesneleri olarak kullanmayın. Diğer bir ifadeyle, C# veya Visual Basic gibi SyncLock(GetType(X)) kodlardan lock(typeof(X)) veya nesnelerle Type kullanımından Monitor.Enter kaçının. Belirli bir tür için uygulama etki alanı başına yalnızca bir örneği System.Type vardır. Kilitlediğiniz tür genelse, kendi kodunuz dışındaki kod kilitler alabilir ve kilitlenmelere yol açabilir. Ek sorunlar için bkz . Güvenilirlik en iyi yöntemleri.

  • Örneğin lock(this) C# veya SyncLock(Me) Visual Basic'te örnekleri kilitlerken dikkatli olun. Uygulamanızda türü dışında olan diğer kodlar nesnede kilitlenirse kilitlenmeler oluşabilir.

  • İzleyiciye giren bir iş parçacığının, iş parçacığı monitördeyken bir özel durum oluşsa bile her zaman bu izleyiciden ayrıldığından emin olun. C# lock deyimi ve Visual Basic SyncLock deyimi, çağrıldığından Monitor.Exit emin olmak için bir finally bloğu kullanarak bu davranışı otomatik olarak sağlar. Exit'in çağrılmasını sağlayamıyorsanız tasarımınızı Mutex kullanacak şekilde değiştirmeyi göz önünde bulundurun. Şu anda sahip olan iş parçacığı sonlandırıldığında bir mutex otomatik olarak serbest bırakılır.

  • Farklı kaynaklar gerektiren görevler için birden çok iş parçacığı kullanın ve tek bir kaynağa birden çok iş parçacığı atamaktan kaçının. Örneğin, G/Ç içeren herhangi bir görev, G/Ç işlemleri sırasında iş parçacığı engellenip diğer iş parçacıklarının yürütülmesine izin verdiği için kendi iş parçacığına sahip olmanın avantajlarından yararlanır. Kullanıcı girişi, ayrılmış iş parçacığından yararlanan başka bir kaynaktır. Tek işlemcili bir bilgisayarda, yoğun hesaplama içeren bir görev kullanıcı girişiyle ve G/Ç içeren görevlerle birlikte bulunur, ancak birden çok işlem yoğunluklu görev birbiriyle bir arada bulunur.

  • deyimini InterlockedSyncLock (Visual Basic'te) kullanmak yerine basit durum değişiklikleri için sınıfının yöntemlerini kullanmayı lock göz önünde bulundurun. deyimi lock iyi bir genel amaçlı araçtır, ancak Interlocked sınıfı atomik olması gereken güncelleştirmeler için daha iyi performans sağlar. İç olarak, çekişme yoksa tek bir kilit ön eki yürütür. Kod incelemelerinde, aşağıdaki örneklerde gösterilen gibi kodu izleyin. İlk örnekte durum değişkeni artırılır:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)
    {  
        myField++;  
    }  
    

    Aşağıdaki gibi deyimi yerine lock yöntemini kullanarak Increment performansı geliştirebilirsiniz:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Not

    1'den Add büyük atomik artışlar için yöntemini kullanın.

    İkinci örnekte, başvuru türü değişkeni yalnızca null başvuruysa (Nothing Visual Basic'te) güncelleştirilir.

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            x ??= y;
        }  
    }  
    

    Performans, aşağıdaki gibi yöntemi kullanılarak CompareExchange geliştirilebilir:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Not

    Yöntem CompareExchange<T>(T, T, T) aşırı yüklemesi, başvuru türleri için tür açısından güvenli bir alternatif sağlar.

Sınıf kitaplıkları için Öneriler

Çoklu iş parçacığı kullanımı için sınıf kitaplıkları tasarlarken aşağıdaki yönergeleri göz önünde bulundurun:

  • Mümkünse eşitleme gereksiniminden kaçının. Bu özellikle yoğun kullanılan kodlar için geçerlidir. Örneğin bir algoritma, bir yarış durumunu ortadan kaldırmak yerine tolere etmek için ayarlanabilir. Gereksiz eşitleme performansı düşürür ve kilitlenme ve yarış koşulları olasılığını oluşturur.

  • Statik verileri (Shared Visual Basic'te) iş parçacığını varsayılan olarak güvenli hale getirin.

  • Örnek veri iş parçacığını varsayılan olarak güvenli hale getirmeyin. İş parçacığı güvenli kod oluşturmak için kilit eklemek performansı azaltır, kilit çekişmesi artırır ve kilitlenmelerin oluşma olasılığını oluşturur. Ortak uygulama modellerinde, aynı anda yalnızca bir iş parçacığı kullanıcı kodunu yürütür ve bu da iş parçacığı güvenliği gereksinimini en aza indirir. Bu nedenle, .NET sınıf kitaplıkları varsayılan olarak iş parçacığı güvenli değildir.

  • Statik durumu değiştiren statik yöntemler sağlamaktan kaçının. Yaygın sunucu senaryolarında statik durum istekler arasında paylaşılır ve bu da birden çok iş parçacığının bu kodu aynı anda yürütebileceği anlamına gelir. Bu, hataların iş parçacığı oluşturma olasılığını açar. verileri istekler arasında paylaşılmayan örneklere kapsülleyen bir tasarım deseni kullanmayı göz önünde bulundurun. Ayrıca, statik veriler eşitlenirse, durumu değiştiren statik yöntemler arasındaki çağrılar kilitlenmelere veya yedekli eşitlemeye neden olabilir ve performansı olumsuz etkileyebilir.

Ayrıca bkz.