Lock (this) {…} neden kötü?


484

MSDN belgelerine diyor

public class SomeObject
{
  public void SomeOperation()
  {
    lock(this)
    {
      //Access instance variables
    }
  }
}

"örneğe genel olarak erişilebiliyorsa bir sorundur". Nedenini merak ediyorum? Kilit gereğinden fazla tutulacağı için mi? Yoksa daha sinsi bir sebep var mı?

Yanıtlar:


508

Kullanmak kötü bir form thisKilit ifadelerinde çünkü bu nesneyi başka kimin kilitleyebileceği genellikle sizin kontrolünüz dışındadır.

Paralel işlemleri düzgün bir şekilde planlamak için olası kilitlenme durumlarını dikkate almak için özel dikkat gösterilmelidir ve bilinmeyen sayıda kilit giriş noktasına sahip olmak bunu engeller. Örneğin, nesneye referansı olan herhangi biri, nesne tasarımcısı / yaratıcısı bunu bilmeden üzerinde kilitlenebilir. Bu, çok iş parçacıklı çözümlerin karmaşıklığını artırır ve bunların doğruluğunu etkileyebilir.

Özel alan genellikle daha iyi bir seçenektir, çünkü derleyici kendisine erişim kısıtlamalarını uygular ve kilitleme mekanizmasını kapsül içine alır. Kullanmak this, kilitleme uygulamanızın bir kısmını halka açığa çıkararak kapsüllemeyi ihlal eder. thisBelgelenmedikçe bir kilit alacağınız da açık değildir . O zaman bile, bir sorunu önlemek için belgelere güvenmek en uygunudur.

Son olarak, lock(this)parametre olarak iletilen nesneyi gerçekten değiştiren ve bir şekilde onu salt okunur veya erişilemez hale getiren yaygın bir yanlış anlama vardır . Bu yanlış . lockYalnızca anahtar olarak işlev görmek üzere parametre olarak iletilen nesne . Bu tuş üzerinde zaten bir kilit tutuluyorsa, kilit yapılamaz; aksi takdirde kilide izin verilir.

Bu nedenle lock, değişmez oldukları ve uygulamanın bazı bölümlerinde paylaşıldığı / erişilebilir olduğu için, ifadelerde anahtar olarak dize kullanmak kötüdür . Bunun yerine özel bir değişken kullanmalısınız, bir Objectörnek güzel olacaktır.

Örnek olarak aşağıdaki C # kodunu çalıştırın.

public class Person
{
    public int Age { get; set;  }
    public string Name { get; set; }

    public void LockThis()
    {
        lock (this)
        {
            System.Threading.Thread.Sleep(10000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var nancy = new Person {Name = "Nancy Drew", Age = 15};
        var a = new Thread(nancy.LockThis);
        a.Start();
        var b = new Thread(Timewarp);
        b.Start(nancy);
        Thread.Sleep(10);
        var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
        var c = new Thread(NameChange);
        c.Start(anotherNancy);
        a.Join();
        Console.ReadLine();
    }

    static void Timewarp(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // A lock does not make the object read-only.
        lock (person.Name)
        {
            while (person.Age <= 23)
            {
                // There will be a lock on 'person' due to the LockThis method running in another thread
                if (Monitor.TryEnter(person, 10) == false)
                {
                    Console.WriteLine("'this' person is locked!");
                }
                else Monitor.Exit(person);
                person.Age++;
                if(person.Age == 18)
                {
                    // Changing the 'person.Name' value doesn't change the lock...
                    person.Name = "Nancy Smith";
                }
                Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
            }
        }
    }

    static void NameChange(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // You should avoid locking on strings, since they are immutable.
        if (Monitor.TryEnter(person.Name, 30) == false)
        {
            Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
        }
        else Monitor.Exit(person.Name);

        if (Monitor.TryEnter("Nancy Drew", 30) == false)
        {
            Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
        }
        else Monitor.Exit("Nancy Drew");
        if (Monitor.TryEnter(person.Name, 10000))
        {
            string oldName = person.Name;
            person.Name = "Nancy Callahan";
            Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
        }
        else Monitor.Exit(person.Name);
    }
}

Konsol çıkışı

'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.

2
Ben grok olarak: (1) Nancy kilit ile iplik 1 içinde (bu). (2) AYNI Nancy hala thread1 kilitliyken thread2 yaşlanıyor - kilitli bir nesnenin salt okunur olmadığını kanıtlamak. AYRICA (2a) iplik 2'de iken, bu Nancy nesnesi Ad üzerinde de kilitlenir. (3) Aynı Ada sahip FARKLI bir nesne oluşturun . (4) thread3'e geçin ve Name ile kilitlemeye çalışın. (büyük bitiş) AMA "dizeler değişmez" anlamına gelir "Nancy Drew" dizesine başvuran herhangi bir nesne, bellekte tam olarak aynı dize örneğine bakmaktadır. Object1 aynı değer üzerine kilitlendiğinde object2 bir ipe bir tespit edemedim Yani
radarbob

Bunun yerine standart bir değişken kullanmak lock(this)standart tavsiyedir; dış kodun nesne ile ilişkili kilidin yöntem çağrıları arasında tutulmasına neden olmasını genellikle imkansız hale getireceğine dikkat etmek önemlidir. Bu iyi bir şey olabilir veya olmayabilir . Dış kodun keyfi bir süre için kilit tutmasına izin verme konusunda bazı tehlikeler vardır ve sınıflar genellikle bu tür bir kullanımı gereksiz kılacak şekilde tasarlanmalıdır, ancak her zaman pratik alternatifler yoktur. Basit bir örnek olarak, bir koleksiyon kendi başına bir yöntem ToArrayveya ToListyöntem uygulamadığı sürece ...
supercat

4
("IEnumerable <T> uzatma yöntemlerinin aksine), koleksiyonun anlık görüntüsünü almak isteyen bir iş parçacığının tek yolu tüm değişiklikleri kilitlerken numaralandırmak olabilir . Bunu yapabilmek için, koleksiyonu değiştirecek herhangi bir kod tarafından alınan bir kilide erişimi olmalıdır. Kilidin açığa çıkmaması, programın düzenli olarak koleksiyonun eşzamansız anlık görüntüsünü gerçekleştirmesini imkansız hale getirebilir (örneğin, koleksiyon tarama kullanıcı arayüzünü güncellemek için).
supercat

there is the common misconception that lock(this) actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false- Bu görüşmelerin CLR nesnesinde SyncBlock bitiyle ilgili olduğuna inanıyorum, bu yüzden resmi olarak bu sağ kilitli modifiye nesnenin kendisidir
sll

@Ebeban, örneğini kesinlikle seviyorum, harika. Sana bir sorum var. NameChange (..) yönteminin kodunuz şu şekilde bitiyor: <code> if (Monitor.TryEnter (person.Name, 10000)) {. . . } başka Monitor.Exit (person.Name); </code> Eğer bitmiyorsa: <code> if (Monitor.TryEnter (person.Name, 10000)) {. . . Monitor.Exit (person.Name); } </code>
AviFarah

64

Çünkü insanlar nesne örneği (yani: sizin this) işaretçinize ulaşabilirse, aynı nesneyi kilitlemeyi de deneyebilirler. Şimdi thisdahili olarak kilitlediğinizin farkında olmayabilirler, bu da sorunlara neden olabilir (muhtemelen bir kilitlenme)

Buna ek olarak, aynı zamanda kötü bir uygulamadır, çünkü "çok fazla" kilitliyor

Örneğin, bir üye değişkeniniz olabilir List<int>ve aslında kilitlemeniz gereken tek şey o üye değişkenidir. Tüm nesneyi işlevlerinizde kilitlerseniz, bu işlevleri çağıran diğer şeyler kilit beklenir. Bu işlevlerin üye listesine erişmesi gerekmiyorsa, başka bir kodun başvurunuzu beklemesine ve yavaşlamasına neden olursunuz.


44
Bu cevabın son paragrafı doğru değil. Kilit, nesneyi hiçbir şekilde erişilemez veya salt okunur yapmaz. Kilitle (bu), başka bir iş parçacığının bu nesnenin başvurduğu nesneyi çağırmasını veya değiştirmesini engellemez.
Esteban Brenes

3
Çağrılan diğer yöntemler de bir kilit yaparsa yapar (bu). İnanıyorum ki yaptığı nokta bu. "Tüm nesneleri fonksiyonlarınızda kilitlerseniz" ...
Herms

@Orion: Bu daha açık. @Herms: Evet, ancak bu işlevselliği elde etmek için 'this' kullanmanıza gerek yok, listelerdeki SyncRoot özelliği bu amaca hizmet ediyor, örneğin, bu anahtar üzerinde net senkronizasyon yapılması gerekiyor.
Esteban Brenes

Re: "çok fazla" kilitleme: Neyin kilitleneceğine karar veren ince bir dengeleme eylemidir. Bir kilit almanın önbellek yıkama CPU işlemlerini içerdiğini ve biraz pahalı olduğunu unutmayın. Başka bir deyişle: her bir tamsayıyı kilitlemeyin ve güncellemeyin. :)
Zan Lynx

Son paragraf hala mantıklı değil. Yalnızca listeye erişimi kısıtlamanız gerekiyorsa, neden listeye erişmezlerse diğer işlevlerin kilitleri olur?
Joakim MH

44

MSDN Konu İş Parçacığı Eşitlemesine bir göz atın (C # Programlama Kılavuzu)

Genel olarak, genel bir türe veya uygulamanızın kontrolü dışındaki nesne örneklerine kilitlenmekten kaçınmak en iyisidir. Örneğin, örneğe genel olarak erişilebiliyorsa lock (this) sorunlu olabilir, çünkü sizin kontrolünüz dışındaki kodlar da nesneyi kilitleyebilir. Bu, iki veya daha fazla iş parçacığının aynı nesnenin serbest bırakılmasını beklediği kilitlenme durumları oluşturabilir. Genel bir veri türünün kilitlenmesi, bir nesnenin aksine, aynı nedenden dolayı sorunlara neden olabilir. Değişmez dizeler üzerinde kilitleme özellikle risklidir çünkü değişmez dizeler ortak dil çalışma zamanı (CLR) tarafından staj edilir. Bu, tüm program için belirli bir dize değişmezinin bir örneği olduğu anlamına gelir; tam olarak aynı nesne, çalışan tüm uygulama etki alanlarında, tüm iş parçacıklarında değişmez değeri temsil eder. Sonuç olarak, uygulama işleminin herhangi bir yerinde aynı içeriğe sahip bir dize üzerine yerleştirilen bir kilit, uygulamadaki bu dizenin tüm örneklerini kilitler. Sonuç olarak, stajyer olmayan özel veya korunan bir üyeyi kilitlemek en iyisidir. Bazı sınıflar özellikle kilitleme için üyeler sağlar. Örneğin Array türü SyncRoot sağlar. Birçok koleksiyon türü de bir SyncRoot üyesi sağlar.


34

Bu eski bir iplik olduğunu biliyorum, ama insanlar hala bu kadar bakmak ve ona güvenebilirsiniz, çünkü işaret etmek önemli görünüyor lock(typeof(SomeObject))önemli ölçüde daha kötüdür lock(this). Bunu söyledikten sonra; Alan'ın lock(typeof(SomeObject))kötü uygulama olduğuna işaret ettiği için samimi kudos .

Bir örneği System.Typeolduğu en genel, kaba taneli amaçlarından biridir. En azından, bir System.Type örneği bir AppDomain için geneldir ve .NET bir AppDomain içinde birden fazla program çalıştırabilir. Bu, tamamen farklı iki programın, her ikisi de aynı tür örnekte bir eşitleme kilidi almaya çalıştıklarında, bir kilitlenme yaratma derecesinde bile potansiyel olarak parazite neden olabileceği anlamına gelir.

Bu nedenle lock(this)özellikle sağlam bir form değildir, sorunlara neden olabilir ve belirtilen tüm nedenlerden dolayı daima kaşları kaldırmalıdır. Yine de ben şahsen o kalıp değişikliğini görmeyi tercih etsem de, kilit (bu) kalıbı yaygın olarak kullanan log4net gibi yaygın olarak kullanılan, nispeten saygın ve görünüşte kararlı bir kod var.

Ancak lock(typeof(SomeObject))yepyeni ve gelişmiş bir solucan kutusu açar.

Değer için.


26

... ve aynı argümanlar bu yapı için de geçerlidir:

lock(typeof(SomeObject))

17
lock (typeof (SomeObject)) aslında kilitten (bu) çok daha kötüdür ( stackoverflow.com/a/10510647/618649 ).
Craig

1
Peki, kilit (Application.Current) o zaman bile daha kötü, Ama kim bu aptal şeylerden birini zaten dener ki? lock (bu) mantıklı ve özlü görünüyor, ancak bu diğer örnekler görünmüyor.
Zar Shardan

Bunun lock(this)özellikle mantıklı ve özlü göründüğüne katılmıyorum . Bu çok kaba bir kilittir ve diğer kodlar nesneniz üzerinde bir kilit alabilir ve bu da dahili kodunuzda parazite neden olabilir. Daha ayrıntılı kilitler alın ve daha sıkı kontrol sağlayın. Bunun lock(this)için bundan daha iyi olması lock(typeof(SomeObject)).
Craig

8

Ofisinizde departmanda paylaşılan bir kaynak olan yetenekli bir sekreterinizin olduğunu düşünün. Arada sırada onlara doğru koşuyorsunuz çünkü bir göreviniz var, sadece iş arkadaşlarınızdan birinin daha önce talep etmediğini ummak için. Genellikle sadece kısa bir süre beklemeniz gerekir.

Bakım, paylaşımda bulunduğundan, yöneticiniz müşterilerin sekreteri de doğrudan kullanabileceğine karar verir. Ancak bunun bir yan etkisi vardır: Bir müşteri bu müşteri için çalışırken bunları talep edebilir ve ayrıca görevlerin bir kısmını yürütmek için onlara ihtiyaç duyabilir. Bir çıkmaza neden olur, çünkü iddia etmek artık bir hiyerarşi değildir. Bu, müşterilerin ilk etapta talepte bulunmalarına izin vermeyerek hep birlikte önlenebilirdi.

lock(this)gördüğümüz kadar kötü. Dışarıdaki bir nesne nesneye kilitlenebilir ve sınıfı kimin kullandığını denetlemediğiniz için, herkes onu kilitleyebilir ... Bu, yukarıda açıklandığı gibi tam bir örnektir. Yine, çözüm nesnenin maruziyetini sınırlamaktır. Ancak, bir varsa private, protectedya internalsınıfı zaten nesne üzerinde kilitleme kimin kontrol edebilecek emin kendiniz kodunuzu yazmışsınız çünkü. Buradaki mesaj şudur: onu açıkta bırakmayın public. Ayrıca, benzer senaryolarda bir kilidin kullanılmasını sağlamak kilitlenmeleri önler.

Bunun tam tersi, uygulama alanı boyunca paylaşılan kaynakları kilitlemektir - en kötü durum senaryosu. Sekreterinizi dışarıya koymak ve orada herkesin hak talebinde bulunmasına izin vermek gibi. Sonuç tamamen kaos - ya da kaynak kodu açısından: kötü bir fikirdi; atın ve baştan başlayın. Peki bunu nasıl yapacağız?

Buradaki çoğu kişinin işaret ettiği gibi türler uygulama alanında paylaşılır. Ancak kullanabileceğimiz daha iyi şeyler var: dizeler. Bunun nedeni, dizelerin havuzda toplanmasıdır . Başka bir deyişle: bir uygulama etki alanında aynı içeriğe sahip iki dizeniz varsa, bunların tam olarak aynı işaretçiye sahip olma olasılığı vardır. İşaretçi kilit anahtarı olarak kullanıldığından, temelde aldığınız şey "tanımlanmamış davranışa hazırlanın" ile eşanlamlıdır.

Benzer şekilde, WCF nesneleri, HttpContext.Current, Thread.Current, Singletons (genel olarak), vb. Üzerinde kilitlememelisiniz. private [static] object myLock = new object();


3
Aslında özel bir sınıfa sahip olmak sorunu engellemez. Harici kod özel sınıf örneğine başvuru yapabilir ...
Rashack

1
@ Teknik olarak doğru iken Rashack (bunu belirtmek için +1), benim amacım, örneği kimin kilitlediğini kontrol etmeniz gerektiğiydi. Bunun gibi örnekler geri dönüyor.
atlaste

4

Hedefe kilitlendik bu pointer olabilir kötü bir aşkın kilitleme eğer paylaşılan kaynağın . Paylaşılan kaynak statik bir değişken veya bilgisayarınızdaki bir dosya olabilir - yani sınıfın tüm kullanıcıları arasında paylaşılan bir şey. Bunun nedeni, bu işaretçinin, sınıfınız her başlatıldığında bellekteki bir konuma farklı bir başvuru içermesidir. Yani, bir sınıf örneğinde bunu kilitlemek , bir sınıfın başka örneğinde bunu kilitlemekten farklıdır .

Ne demek istediğimi görmek için bu kodu inceleyin. Bir Konsol uygulamasında ana programınıza aşağıdaki kodu ekleyin:

    static void Main(string[] args)
    {
         TestThreading();
         Console.ReadLine();
    }

    public static void TestThreading()
    {
        Random rand = new Random();
        Thread[] threads = new Thread[10];
        TestLock.balance = 100000;
        for (int i = 0; i < 10; i++)
        {
            TestLock tl = new TestLock();
            Thread t = new Thread(new ThreadStart(tl.WithdrawAmount));
            threads[i] = t;
        }
        for (int i = 0; i < 10; i++)
        {
            threads[i].Start();
        }
        Console.Read();
    }

Aşağıdaki gibi yeni bir sınıf oluşturun.

 class TestLock
{
    public static int balance { get; set; }
    public static readonly Object myLock = new Object();

    public void Withdraw(int amount)
    {
      // Try both locks to see what I mean
      //             lock (this)
       lock (myLock)
        {
            Random rand = new Random();
            if (balance >= amount)
            {
                Console.WriteLine("Balance before Withdrawal :  " + balance);
                Console.WriteLine("Withdraw        : -" + amount);
                balance = balance - amount;
                Console.WriteLine("Balance after Withdrawal  :  " + balance);
            }
            else
            {
                Console.WriteLine("Can't process your transaction, current balance is :  " + balance + " and you tried to withdraw " + amount);
            }
        }

    }
    public void WithdrawAmount()
    {
        Random rand = new Random();
        Withdraw(rand.Next(1, 100) * 100);
    }
}

İşte program kilitleme bir koşu bu .

   Balance before Withdrawal :  100000
    Withdraw        : -5600
    Balance after Withdrawal  :  94400
    Balance before Withdrawal :  100000
    Balance before Withdrawal :  100000
    Withdraw        : -5600
    Balance after Withdrawal  :  88800
    Withdraw        : -5600
    Balance after Withdrawal  :  83200
    Balance before Withdrawal :  83200
    Withdraw        : -9100
    Balance after Withdrawal  :  74100
    Balance before Withdrawal :  74100
    Withdraw        : -9100
    Balance before Withdrawal :  74100
    Withdraw        : -9100
    Balance after Withdrawal  :  55900
    Balance after Withdrawal  :  65000
    Balance before Withdrawal :  55900
    Withdraw        : -9100
    Balance after Withdrawal  :  46800
    Balance before Withdrawal :  46800
    Withdraw        : -2800
    Balance after Withdrawal  :  44000
    Balance before Withdrawal :  44000
    Withdraw        : -2800
    Balance after Withdrawal  :  41200
    Balance before Withdrawal :  44000
    Withdraw        : -2800
    Balance after Withdrawal  :  38400

İşte myLock üzerinde program kilitleme bir çalışma .

Balance before Withdrawal :  100000
Withdraw        : -6600
Balance after Withdrawal  :  93400
Balance before Withdrawal :  93400
Withdraw        : -6600
Balance after Withdrawal  :  86800
Balance before Withdrawal :  86800
Withdraw        : -200
Balance after Withdrawal  :  86600
Balance before Withdrawal :  86600
Withdraw        : -8500
Balance after Withdrawal  :  78100
Balance before Withdrawal :  78100
Withdraw        : -8500
Balance after Withdrawal  :  69600
Balance before Withdrawal :  69600
Withdraw        : -8500
Balance after Withdrawal  :  61100
Balance before Withdrawal :  61100
Withdraw        : -2200
Balance after Withdrawal  :  58900
Balance before Withdrawal :  58900
Withdraw        : -2200
Balance after Withdrawal  :  56700
Balance before Withdrawal :  56700
Withdraw        : -2200
Balance after Withdrawal  :  54500
Balance before Withdrawal :  54500
Withdraw        : -500
Balance after Withdrawal  :  54000

1
örneğinizde dikkat etmeniz gereken şey nedir, neyin yanlış olduğunu gösterdiğiniz gibi. Random rand = new Random();nvm kullandığınızda neyin yanlış olduğunu bulmak zor onun tekrar tekrar baktığını düşünüyorum
Seabizkit

3

Bu konuda çok iyi bir makale var http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects Microsoft® .NET çalışma zamanı performans mimarı Rico Mariani

Alıntı:

Buradaki temel sorun, yazım nesnesine sahip olmamanız ve başka kimin erişebileceğini bilmemenizdir. Genel olarak, oluşturmadığınız ve başka kimin erişebileceğini bilmediğiniz bir nesneyi kilitlemeye güvenmek çok kötü bir fikirdir. Bunu yapmak çıkmazı davet eder. En güvenli yol sadece özel nesneleri kilitlemektir.



2

İşte kilit (bu) neden kötüdür ve sınıfınızın tüketicisi de nesneyi kilitlemeye çalıştığında kilitlenmelere neden olabilecek çok daha basit bir örnek ( burada Soru 34'ten alınmıştır ). Aşağıda, üç iplikten sadece biri devam edebilir, diğer ikisi kilitlenir.

class SomeClass
{
    public void SomeMethod(int id)
    {
        **lock(this)**
        {
            while(true)
            {
                Console.WriteLine("SomeClass.SomeMethod #" + id);
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        SomeClass o = new SomeClass();

        lock(o)
        {
            for (int threadId = 0; threadId < 3; threadId++)
            {
                Thread t = new Thread(() => {
                    o.SomeMethod(threadId);
                        });
                t.Start();
            }

            Console.WriteLine();
        }

Etrafında çalışmak için, bu adam Lock yerine Thread.TryMonitor (zaman aşımı ile) kullandı:

            Monitor.TryEnter(temp, millisecondsTimeout, ref lockWasTaken);
            if (lockWasTaken)
            {
                doAction();
            }
            else
            {
                throw new Exception("Could not get lock");
            }

https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks


Gördüğüm kadarıyla, kilidi (bu) özel örnek üyesindeki bir kilitle değiştirdiğimde, SomeClasshala aynı kilitlenmeyi alıyorum. Ayrıca, ana sınıftaki kilit, Programın başka bir özel örnek üyesinde yapılırsa, aynı kilit oluşur. Yani, bu cevabın yanıltıcı ve yanlış olmadığından emin değilim. Bu davranışı burada görün: dotnetfiddle.net/DMrU5h
Bartosz

1

Çünkü sınıfınızın örneğini görebilen herhangi bir kod parçası da bu referansı kilitleyebilir. Yalnızca referans olması gereken kodun ona başvurabilmesi için kilitleme nesnenizi gizlemek (kapsüllemek) istersiniz. Bu anahtar kelime geçerli sınıf örneğini ifade eder, bu nedenle herhangi bir sayıya referans verilebilir ve bunu iş parçacığı senkronizasyonu için kullanabilir.

Açıkçası, bu kötüdür, çünkü diğer bazı kodlar sınıf örneğini kilitlemek için kullanabilir ve kodunuzun zamanında kilitlenmesini engelleyebilir veya diğer iş parçacığı senkronizasyon sorunları oluşturabilir. En iyi durum: Kilitlemek için başka hiçbir şey sınıfınıza referans kullanmaz. Orta durumda: bir şey kilitleri yapmak için sınıfınıza bir referans kullanır ve performans sorunlarına neden olur. En kötü durum: kilitler yapmak için bir şey sınıfınızın bir referansını kullanır ve gerçekten kötü, gerçekten ince, gerçekten hata ayıklaması zor sorunlara neden olur.


1

Üzgünüm ama bunu kilitlemenin kilitlenmeye neden olabileceği iddiasına katlanamıyorum. İki şeyi karıştırıyorsunuz: kilitlenme ve açlıktan ölme.

  • Bir kilitlenmeden sonra kilitlenmeyi iptal edemezsiniz, böylece kilitlenmeye girdikten sonra dışarı çıkamazsınız
  • İpliklerden biri işini bitirdikten sonra açlıktan otomatik olarak sona erecek

İşte farkı gösteren bir resim.

Sonuç Eğer iplik açlığı sizin için sorun değilse
yine de güvenle kullanabilirsiniz lock(this). Hâlâ aklınızda bulundurmanız gerekir ki, ipliği, lock(this)nesneyi kilitleyen bir kilitte ipliği kullanarak aç bıraktığınız zaman, sonunda sonsuz açlıkla sonuçlanacaktır;)


9
Bir fark var ama bu tartışma ile tamamen alakasız. Ve sonucunuzun ilk cümlesi tamamen yanlıştır.
Ben Voigt

1
Açık olmak gerekirse: Ben savunmuyorum lock(this)- bu tür bir kod sadece yanlış. Sadece kilitlenme demenin biraz küfürlü olduğunu düşünüyorum.
SOReader

2
Resme bağlantı artık mevcut değil. :( Yeniden
başvurma


1

İzlenmesi daha kolay olan bazı örnek kodlar (IMO): ( LinqPad'de çalışacak , aşağıdaki ad alanlarına başvurulmalıdır: System.Net ve System.Threading.Tasks)

Hatırlanması gereken bir şey, kilit (x) 'nin temelde sözdizimsel şeker olması ve bunun ne olduğunu Monitor.Enter kullanmak ve daha sonra bir dene, yakala ve son olarak Monitor.Exit'i çağırmak için engellemesidir. Bkz. Https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (açıklamalar bölümü)

veya Enter ve Exit yöntemlerini try… nihayet bloğunda saran C # lock deyimini (Visual Basic'te SyncLock deyimi) kullanın.

void Main()
{
    //demonstrates why locking on THIS is BADD! (you should never lock on something that is publicly accessible)
    ClassTest test = new ClassTest();
    lock(test) //locking on the instance of ClassTest
    {
        Console.WriteLine($"CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        Parallel.Invoke(new Action[]
        {
            () => {
                //this is there to just use up the current main thread. 
                Console.WriteLine($"CurrentThread {Thread.CurrentThread.ManagedThreadId}");
                },
            //none of these will enter the lock section.
            () => test.DoWorkUsingThisLock(1),//this will dead lock as lock(x) uses Monitor.Enter
            () => test.DoWorkUsingMonitor(2), //this will not dead lock as it uses Montory.TryEnter
        });
    }
}

public class ClassTest
{
    public void DoWorkUsingThisLock(int i)
    {
        Console.WriteLine($"Start ClassTest.DoWorkUsingThisLock {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        lock(this) //this can be bad if someone has locked on this already, as it will cause it to be deadlocked!
        {
            Console.WriteLine($"Running: ClassTest.DoWorkUsingThisLock {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
        }
        Console.WriteLine($"End ClassTest.DoWorkUsingThisLock Done {i}  CurrentThread {Thread.CurrentThread.ManagedThreadId}");
    }

    public void DoWorkUsingMonitor(int i)
    {
        Console.WriteLine($"Start ClassTest.DoWorkUsingMonitor {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        if (Monitor.TryEnter(this))
        {
            Console.WriteLine($"Running: ClassTest.DoWorkUsingMonitor {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            Monitor.Exit(this);
        }
        else
        {
            Console.WriteLine($"Skipped lock section!  {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        }

        Console.WriteLine($"End ClassTest.DoWorkUsingMonitor Done {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine();
    }
}

Çıktı

CurrentThread 15
CurrentThread 15
Start ClassTest.DoWorkUsingMonitor 2 CurrentThread 13
Start ClassTest.DoWorkUsingThisLock 1 CurrentThread 12
Skipped lock section!  2 CurrentThread 13
End ClassTest.DoWorkUsingMonitor Done 2 CurrentThread 13

12 numaralı iş parçacığının ölü kilitli olduğu zaman asla bitmediğine dikkat edin.


1
ikinci DoWorkUsingThisLockkonuyu sorunu göstermek için gerekli değil gibi görünüyor ?
Jack Lu

outter kilidi ana demek istemiyor musunuz, bir iplik sadece diğerinin tamamlanmasını bekler misiniz? Bu da Paralel'i geçersiz kılacaktır ... daha iyi gerçek dünya örneklerine ihtiyacımız olduğunu hissediyorum ..
Seabizkit

@Seabizkit, kodu biraz daha açık hale getirmek için güncelledi. Paralel, yeni bir iş parçacığı oluşturmak ve kodu eşzamansız olarak çalıştırmak için oradadır. Gerçekte, 2. iş parçacığı herhangi bir şekilde (düğme tıklaması, ayrı istek, vb.)
Raj Rao

0

Bir sınıfın 'this' ya da sınıftaki kodun başlattığı herhangi bir nesneye kilitlenen bir kodu olabileceğini söyleyen bir kural oluşturabilirsiniz. Bu yüzden, sadece kalıp takip edilmezse bir problemdir.

Kendinizi bu kalıba uymayan kodlardan korumak istiyorsanız, kabul edilen cevap doğrudur. Ancak eğer desen takip edilirse, bu bir sorun değildir.

Kilidin (bu) avantajı verimliliktir. Tek bir değeri tutan basit bir "değer nesneniz" varsa ne olur? Bu sadece bir sarıcı ve milyonlarca kez somutlaştırılıyor. Sadece kilitleme için özel bir senkronizasyon nesnesi oluşturulmasını gerektirerek, temel olarak nesnenin boyutunu iki katına çıkardınız ve ayırma sayısını iki katına çıkardınız. Performans önemli olduğunda, bu bir avantajdır.

Tahsis sayısını veya bellek ayak izini umursamıyorsanız, diğer yanıtlarda belirtilen nedenlerden dolayı kilitten (bu) kaçınmak tercih edilir.


-1

Aynı nesne örneğini kullanıyor olabilecek başka istekler olabileceğinden, örneğe genel olarak erişilebiliyorsa bir sorun olacaktır. Özel / statik değişken kullanmak daha iyidir.


5
Bunun insana neyi eklediğinden emin değilim, aynı şeyi söyleyen ayrıntılı cevaplar zaten var.
Andrew Barber
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.