Mengapa kunci (ini) {...} buruk?

484

The dokumentasi MSDN mengatakan bahwa

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

adalah "masalah jika instance dapat diakses secara publik". Saya bertanya-tanya mengapa? Apakah karena kunci akan ditahan lebih lama dari yang diperlukan? Atau adakah alasan yang lebih berbahaya?

Anton
sumber

Jawaban:

508

Ini adalah bentuk yang buruk untuk digunakan thisdalam pernyataan kunci karena umumnya di luar kendali Anda siapa lagi yang mungkin mengunci objek itu.

Untuk merencanakan operasi paralel dengan semestinya, perhatian khusus harus diberikan untuk mempertimbangkan kemungkinan situasi jalan buntu, dan memiliki jumlah titik masuk kunci yang tidak diketahui menghalangi hal ini. Misalnya, siapa pun yang memiliki referensi ke objek dapat menguncinya tanpa perancang / pembuat objek mengetahuinya. Ini meningkatkan kompleksitas solusi multi-utas dan mungkin memengaruhi kebenarannya.

Bidang pribadi biasanya merupakan opsi yang lebih baik karena kompiler akan memberlakukan pembatasan akses padanya, dan itu akan merangkum mekanisme penguncian. Menggunakan thismelanggar enkapsulasi dengan memaparkan sebagian dari implementasi penguncian Anda kepada publik. Juga tidak jelas bahwa Anda akan mendapatkan kunci thiskecuali jika telah didokumentasikan. Bahkan kemudian, mengandalkan dokumentasi untuk mencegah masalah tidak optimal.

Akhirnya, ada kesalahpahaman umum yang lock(this)benar - benar memodifikasi objek yang dilewatkan sebagai parameter, dan dalam beberapa cara membuatnya hanya-baca atau tidak dapat diakses. Ini salah . Objek diteruskan sebagai parameter lockhanya berfungsi sebagai kunci . Jika kunci sudah ditahan pada kunci itu, kunci tidak dapat dibuat; jika tidak, kunci diizinkan.

Inilah sebabnya mengapa buruk untuk menggunakan string sebagai kunci dalam lockpernyataan, karena mereka tidak dapat diubah dan dibagikan / diakses di seluruh bagian aplikasi. Anda harus menggunakan variabel pribadi sebagai gantinya, sebuah Objectinstance akan melakukannya dengan baik.

Jalankan kode C # berikut sebagai contoh.

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

Output konsol

'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'.
Esteban Brenes
sumber
2
Seperti yang saya grok: (1) Nancy di thread1 dengan kunci (ini). (2) SAMA Nancy dalam penuaan thread2 sementara masih terkunci di thread1 - membuktikan objek terkunci tidak hanya baca. JUGA (2a) saat berada di utas 2, objek Nancy ini juga dikunci pada Nama. (3) Buat objek BERBEDA dengan Nama yang sama . (4) Lewati thread3 & upaya untuk mengunci dengan Nama. (selesai besar) TETAPI "string is immutable" yang berarti objek apa pun yang mereferensikan string "Nancy Drew" sedang melihat contoh string yang sama dalam memori. Jadi object2 tidak bisa mendapatkan kunci pada string ketika object1 terkunci pada nilai yang sama
radarbob
Menggunakan variabel standar sebagai ganti lock(this)saran standar; Penting untuk dicatat bahwa melakukan hal itu biasanya akan membuat kode luar tidak mungkin menyebabkan kunci yang terkait dengan objek ditahan di antara pemanggilan metode. Ini mungkin atau mungkin bukan hal yang baik . Ada beberapa bahaya dalam membiarkan kode luar menahan kunci untuk durasi yang sewenang-wenang, dan kelas umumnya harus dirancang untuk membuat penggunaan seperti itu tidak perlu, tetapi tidak selalu ada alternatif praktis. Sebagai contoh sederhana, kecuali sebuah koleksi mengimplementasikan ToArrayatau ToListmetode miliknya sendiri ...
supercat
4
(sebagai lawan dari metode ekstensi `IEnumerable <T>), satu-satunya cara untuk utas yang menginginkan snapshot koleksi untuk mendapatkannya adalah dengan menghitungnya sambil mengunci semua perubahan . Untuk melakukan itu, ia harus memiliki akses ke kunci yang diperoleh oleh kode apa pun yang akan mengubah koleksi. Kegagalan untuk mengekspos kunci mungkin membuat mustahil untuk mis. Membuat program secara berkala melakukan snapshot asinkron dari koleksi (misalnya untuk memperbarui antarmuka pengguna browsing-koleksi).
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- Saya percaya pembicaraan itu tentang bit SyncBlock di objek CLR jadi secara resmi ini adalah objek yang dimodifikasi dengan benar
sll
@Esteban, saya benar-benar menyukai teladan Anda, sangat mengagumkan. Saya memiliki pertanyaan untuk Anda. Kode metode NameChange (..) Anda berakhir dengan: <code> if (Monitor.TryEnter (person.Name, 10000)) {. . . } else Monitor.Exit (person.Name); </code> Jika tidak diakhiri dengan: <code> if (Monitor.TryEnter (person.Name, 10000)) {. . . Monitor.Keluar (person.Name); } </code>
AviFarah
64

Karena jika orang bisa mendapatkan thispointer objek Anda (yaitu: Anda ), maka mereka juga dapat mencoba untuk mengunci objek yang sama. Sekarang mereka mungkin tidak menyadari bahwa Anda mengunci secara thisinternal, jadi ini dapat menyebabkan masalah (mungkin jalan buntu)

Selain itu, ini juga praktik yang buruk, karena mengunci "terlalu banyak"

Misalnya, Anda mungkin memiliki variabel anggota List<int>, dan satu-satunya hal yang perlu Anda kunci adalah variabel anggota itu. Jika Anda mengunci seluruh objek di fungsi Anda, maka hal-hal lain yang memanggil fungsi-fungsi itu akan diblokir menunggu kunci. Jika fungsi-fungsi itu tidak perlu mengakses daftar anggota, Anda akan menyebabkan kode lain menunggu dan memperlambat aplikasi Anda tanpa alasan sama sekali.

Orion Edwards
sumber
44
Paragraf terakhir dari jawaban ini tidak benar. Kunci tidak dengan cara apa pun membuat objek tidak dapat diakses atau hanya-baca. Kunci (ini) tidak mencegah utas lainnya memanggil atau memodifikasi objek yang dirujuk oleh ini.
Esteban Brenes
3
Itu terjadi jika metode lain yang dipanggil juga melakukan kunci (ini). Saya percaya itulah maksudnya. Perhatikan "Jika Anda mengunci seluruh objek dalam fungsi Anda" ...
Herms
@Orion: Itu lebih jelas. @Human: Ya, tetapi Anda tidak perlu menggunakan 'ini' untuk mencapai fungsionalitas itu, properti SyncRoot dalam daftar melayani tujuan itu, misalnya, sementara itu membuatnya jelas sinkronisasi harus dilakukan pada kunci itu.
Esteban Brenes
Re: mengunci "terlalu banyak": Ini adalah tindakan penyeimbangan yang baik memutuskan apa yang harus dikunci. Perlu diketahui bahwa mengambil kunci melibatkan operasi CPU cache-flush dan agak mahal. Dengan kata lain: jangan mengunci dan memperbarui setiap integer individu. :)
Zan Lynx
Paragraf terakhir masih tidak masuk akal. Jika Anda hanya perlu membatasi akses ke daftar, mengapa fungsi-fungsi lain memiliki kunci jika mereka tidak mengakses daftar?
Joakim MH
44

Lihatlah Sinkronisasi Topik MSDN (Panduan Pemrograman C #)

Secara umum, yang terbaik adalah menghindari penguncian pada tipe publik, atau pada instance objek di luar kendali aplikasi Anda. Sebagai contoh, kunci (ini) dapat menjadi masalah jika instance dapat diakses secara publik, karena kode di luar kendali Anda dapat mengunci objek juga. Ini bisa menciptakan situasi jalan buntu di mana dua atau lebih utas menunggu rilis objek yang sama. Mengunci tipe data publik, yang bertentangan dengan objek, dapat menyebabkan masalah karena alasan yang sama. Mengunci string literal sangat berisiko karena string literal diinternir oleh runtime bahasa umum (CLR). Ini berarti bahwa ada satu contoh string string apa pun yang diberikan untuk seluruh program, objek yang sama persis mewakili literal di semua domain aplikasi yang berjalan, di semua utas. Akibatnya, kunci ditempatkan pada string dengan konten yang sama di mana saja dalam proses aplikasi mengunci semua contoh string itu dalam aplikasi. Akibatnya, yang terbaik adalah mengunci anggota pribadi atau yang dilindungi yang tidak diinternir. Beberapa kelas menyediakan anggota khusus untuk mengunci. Jenis Array, misalnya, menyediakan SyncRoot. Banyak tipe koleksi menyediakan anggota SyncRoot juga.

crashmstr
sumber
34

Saya tahu ini adalah utas lama, tetapi karena orang masih dapat melihat dan mengandalkannya, sepertinya penting untuk menunjukkan bahwa lock(typeof(SomeObject))ini jauh lebih buruk daripada lock(this). Setelah mengatakan itu; pujian tulus kepada Alan karena menunjukkan bahwa itu lock(typeof(SomeObject))adalah praktik yang buruk.

Sebuah instance dari System.Typeadalah salah satu objek yang paling umum, berbutir kasar. Paling tidak, turunan dari System.Type bersifat global ke suatu AppDomain, dan .NET dapat menjalankan banyak program dalam suatu AppDomain. Ini berarti bahwa dua program yang sama sekali berbeda berpotensi menyebabkan gangguan dalam satu sama lain bahkan sampai membuat jalan buntu jika mereka berdua mencoba untuk mendapatkan kunci sinkronisasi pada instance jenis yang sama.

Jadi lock(this)bukan bentuk yang kuat, dapat menyebabkan masalah dan harus selalu mengangkat alis karena semua alasan yang dikutip. Namun ada banyak kode yang digunakan, relatif dihormati dan tampaknya stabil seperti log4net yang menggunakan pola kunci (ini) secara luas, meskipun saya pribadi lebih suka melihat perubahan pola itu.

Tapi lock(typeof(SomeObject))membuka kaleng cacing yang sama sekali baru dan lebih baik.

Untuk apa nilainya.

Craig
sumber
26

... dan argumen yang sama persis berlaku untuk konstruk ini juga:

lock(typeof(SomeObject))
Alan
sumber
17
kunci (typeof (SomeObject)) sebenarnya jauh lebih buruk daripada kunci (ini) ( stackoverflow.com/a/10510647/618649 ).
Craig
1
baik, kunci (Application.Current) bahkan lebih buruk, Tapi siapa yang akan mencoba hal-hal bodoh ini? mengunci (ini) tampaknya logis dan succint, tetapi contoh-contoh lain ini tidak.
Zar Shardan
Saya tidak setuju itu lock(this)tampaknya sangat logis dan ringkas. Ini adalah kunci yang sangat kasar, dan kode lain apa pun dapat mengambil kunci pada objek Anda, yang berpotensi menyebabkan gangguan pada kode internal Anda. Ambil lebih banyak kunci granular, dan asumsikan kontrol lebih ketat. Apa yang lock(this)sebenarnya terjadi adalah jauh lebih baik daripada itu lock(typeof(SomeObject)).
Craig
8

Bayangkan Anda memiliki sekretaris yang terampil di kantor Anda yang merupakan sumber daya bersama di departemen. Sesekali, Anda bergegas ke arah mereka karena Anda memiliki tugas, hanya untuk berharap bahwa rekan kerja Anda yang lain belum mengklaimnya. Biasanya Anda hanya perlu menunggu untuk jangka waktu singkat.

Karena kepedulian berbagi, manajer Anda memutuskan bahwa pelanggan dapat menggunakan sekretaris secara langsung juga. Tetapi ini memiliki efek samping: Seorang pelanggan bahkan mungkin mengklaim mereka saat Anda sedang bekerja untuk pelanggan ini dan Anda juga membutuhkan mereka untuk melaksanakan bagian dari tugas. Kebuntuan terjadi, karena klaim bukan lagi hierarki. Ini bisa dihindari bersama-sama dengan tidak membiarkan pelanggan mengklaimnya sejak awal.

lock(this)buruk seperti yang kita lihat. Objek luar mungkin mengunci objek dan karena Anda tidak mengontrol siapa yang menggunakan kelas, siapa pun dapat menguncinya ... Yang merupakan contoh tepat seperti dijelaskan di atas. Sekali lagi, solusinya adalah membatasi pemaparan objek. Namun, jika Anda memiliki private, protectedatau internalkelas, Anda sudah bisa mengendalikan siapa yang mengunci objek Anda , karena Anda yakin telah menulis kode sendiri. Jadi pesannya di sini adalah: jangan memaparkannya sebagai public. Juga, memastikan bahwa kunci digunakan dalam skenario yang sama menghindari kebuntuan.

Kebalikan dari ini adalah untuk mengunci sumber daya yang dibagikan di seluruh domain aplikasi - skenario terburuk. Ini seperti menempatkan sekretaris Anda di luar dan membiarkan semua orang di luar sana mengklaimnya. Hasilnya adalah kekacauan total - atau dalam hal kode sumber: itu adalah ide yang buruk; buang dan mulai lagi dari awal. Jadi bagaimana kita melakukannya?

Jenis dibagi dalam domain aplikasi seperti yang ditunjukkan sebagian besar orang di sini. Tetapi ada hal-hal yang lebih baik yang dapat kita gunakan: string. Alasannya adalah bahwa string dikumpulkan . Dengan kata lain: jika Anda memiliki dua string yang memiliki konten yang sama di domain aplikasi, ada kemungkinan bahwa mereka memiliki pointer yang sama persis. Karena pointer digunakan sebagai kunci kunci, yang pada dasarnya Anda dapatkan adalah sinonim untuk "mempersiapkan perilaku yang tidak terdefinisi".

Demikian pula, Anda tidak harus mengunci objek WCF, HttpContext.Current, Thread.Current, Singletons (secara umum), dll. Cara termudah untuk menghindari semua ini? private [static] object myLock = new object();

atlaste
sumber
3
Sebenarnya memiliki kelas privat tidak mencegah masalah. Kode eksternal dapat memperoleh referensi ke instance dari kelas privat ...
Rashack
1
@Rashack ketika Anda secara teknis benar (+1 untuk menunjukkan itu), poin saya adalah bahwa Anda harus mengendalikan siapa yang mengunci instance. Mengembalikan contoh seperti itu mematahkannya.
atlaste
4

Mengunci pointer ini bisa buruk jika Anda mengunci sumber daya bersama . Sumber daya yang dibagikan dapat berupa variabel statis atau file di komputer Anda - yaitu sesuatu yang dibagikan di antara semua pengguna kelas. Alasannya adalah bahwa pointer ini akan berisi referensi yang berbeda ke lokasi di memori setiap kali kelas Anda dipakai. Jadi, mengunci ini sekaligus contoh kelas berbeda dari mengunci ini pada instance lain dari suatu kelas.

Lihat kode ini untuk melihat apa yang saya maksud. Tambahkan kode berikut ke program utama Anda di aplikasi Konsol:

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

Buat kelas baru seperti di bawah ini.

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

Berikut ini adalah program penguncian ini .

   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

Berikut ini adalah program penguncian di myLock .

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
ItsAllABadJoke
sumber
1
apa yang harus diperhatikan dalam contoh Anda, seperti apa yang Anda tunjukkan yang salah. sulit untuk menemukan apa yang salah ketika Anda menggunakan Random rand = new Random();nvm saya pikir saya melihat itu saldo berulang
Seabizkit
3

Ada artikel yang sangat bagus tentang hal itu http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects oleh Rico Mariani, arsitek kinerja untuk runtime Microsoft® .NET

Kutipan:

Masalah mendasar di sini adalah bahwa Anda tidak memiliki objek jenis, dan Anda tidak tahu siapa lagi yang bisa mengaksesnya. Secara umum, ini adalah ide yang sangat buruk untuk mengandalkan mengunci objek yang tidak Anda buat dan tidak tahu siapa lagi yang mengakses. Melakukan hal itu mengundang kebuntuan. Cara teraman adalah dengan hanya mengunci objek pribadi.

vikrant
sumber
2

Berikut adalah ilustrasi yang jauh lebih sederhana (diambil dari Pertanyaan 34 di sini ) mengapa kunci (ini) buruk dan dapat mengakibatkan kebuntuan ketika konsumen kelas Anda juga mencoba untuk mengunci objek. Di bawah ini, hanya satu dari tiga utas yang dapat dilanjutkan, dua lainnya mengalami kebuntuan.

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

Untuk menyiasatinya, orang ini menggunakan Thread.TryMonitor (dengan waktu habis) alih-alih mengunci:

            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

user3761555
sumber
Sejauh yang saya lihat, ketika saya mengganti kunci (ini) dengan kunci pada anggota contoh pribadi SomeClass, saya masih mendapatkan kebuntuan yang sama. Juga, jika kunci di kelas utama dilakukan pada anggota instance pribadi lain dari Program, kunci yang sama terjadi. Jadi, tidak yakin apakah jawaban ini tidak menyesatkan dan salah. Lihat perilaku itu di sini: dotnetfiddle.net/DMrU5h
Bartosz
1

Karena setiap potongan kode yang dapat melihat instance kelas Anda juga dapat mengunci referensi itu. Anda ingin menyembunyikan (mengenkapsulasi) objek penguncian Anda sehingga hanya kode yang perlu referensi yang dapat merujuknya. Kata kunci ini merujuk pada turunan kelas saat ini, sehingga sejumlah hal dapat merujuknya dan dapat menggunakannya untuk melakukan sinkronisasi utas.

Agar jelas, ini buruk karena beberapa potongan kode lain dapat menggunakan instance kelas untuk mengunci, dan mungkin mencegah kode Anda mendapatkan kunci tepat waktu atau dapat membuat masalah sinkronisasi utas lainnya. Kasus terbaik: tidak ada lagi yang menggunakan referensi ke kelas Anda untuk mengunci. Huruf tengah: sesuatu menggunakan referensi ke kelas Anda untuk melakukan penguncian dan itu menyebabkan masalah kinerja. Kasus terburuk: sesuatu menggunakan referensi kelas Anda untuk melakukan penguncian dan itu menyebabkan masalah yang sangat buruk, sangat halus, sangat sulit untuk di-debug.

Jason Jackson
sumber
1

Maaf teman-teman tapi saya tidak setuju dengan argumen bahwa mengunci ini dapat menyebabkan kebuntuan. Anda membingungkan dua hal: jalan buntu dan kelaparan.

  • Anda tidak dapat membatalkan kebuntuan tanpa mengganggu salah satu utas jadi setelah Anda memasuki kebuntuan Anda tidak bisa keluar
  • Kelaparan akan berakhir secara otomatis setelah salah satu utas menyelesaikan tugasnya

Berikut adalah gambar yang menggambarkan perbedaannya.

Kesimpulan
Anda masih dapat menggunakan dengan aman lock(this)jika kekosongan utas bukan masalah bagi Anda. Anda masih harus ingat bahwa ketika utas, yaitu utas kelaparan menggunakan lock(this)ujung dalam kunci yang mengunci objek Anda, akhirnya akan berakhir pada kelaparan abadi;)

SOReader
sumber
9
Ada perbedaan tetapi sama sekali tidak relevan dengan diskusi ini. Dan kalimat pertama kesimpulan Anda salah.
Ben Voigt
1
Untuk lebih jelasnya: Saya tidak membela lock(this)- kode semacam ini benar-benar salah. Saya hanya berpikir menyebutnya kebuntuan sedikit kasar.
Pemimpin
2
Tautan ke gambar tidak lagi tersedia. :( Apakah ada kemungkinan Anda dapat merujuk ulang? Thx
VG1
1

Silakan merujuk ke tautan berikut yang menjelaskan mengapa kunci (ini) bukan ide yang baik.

http://blogs.msdn.com/b/bclteam/archive/2004/01/20/60719.aspx

Jadi solusinya adalah menambahkan objek pribadi, misalnya, lockObject ke kelas dan tempatkan wilayah kode di dalam pernyataan kunci seperti yang ditunjukkan di bawah ini:

lock (lockObject)
{
...
}
Dhruv Rangunwala
sumber
tautannya tidak lagi valid.
Rauld
1

Berikut ini beberapa contoh kode yang lebih mudah diikuti (IMO): (Akan berfungsi di LinqPad , rujuk ruang nama berikut: System.Net dan System.Threading.Tasks)

Yang perlu diingat adalah kunci itu (x) pada dasarnya adalah gula sintaksis dan apa yang dilakukannya adalah menggunakan Monitor. Masukkan dan kemudian gunakan coba, tangkap, akhirnya blokir untuk memanggil Monitor. Keluar. Lihat: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (bagian keterangan)

atau gunakan pernyataan kunci C # (pernyataan SyncLock dalam Visual Basic), yang membungkus metode Enter dan Exit dalam blok coba… akhirnya.

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

Keluaran

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

Perhatikan bahwa Thread # 12 tidak pernah berakhir karena mati terkunci.

Raj Rao
sumber
1
Tampaknya DoWorkUsingThisLockutas kedua tidak perlu untuk menggambarkan masalah ini?
Jack Lu
bukan maksud Anda kunci outter di utama, satu thread hanya akan menunggu yang lain untuk menyelesaikan? yang kemudian akan membatalkan Paralel ... saya merasa kita perlu contoh dunia nyata yang lebih baik ..
Seabizkit
@ Seabizkit, perbarui kode untuk membuatnya sedikit lebih jelas. Paralel ada di sana hanya untuk membuat utas baru dan menjalankan kode secara tidak sinkron. Pada kenyataannya, utas ke-2 bisa saja dipanggil dengan berbagai cara (klik tombol, permintaan terpisah, dll).
Raj Rao
0

Anda bisa menetapkan aturan yang mengatakan bahwa kelas bisa memiliki kode yang mengunci 'ini' atau objek apa pun yang kode di kelas instantiate. Jadi itu hanya masalah jika polanya tidak diikuti.

Jika Anda ingin melindungi diri dari kode yang tidak mengikuti pola ini, maka jawaban yang diterima benar. Tetapi jika polanya diikuti, itu tidak masalah.

Keuntungan dari penguncian (ini) adalah efisiensi. Bagaimana jika Anda memiliki "objek nilai" sederhana yang memiliki nilai tunggal. Itu hanya pembungkus, dan akan dipakai jutaan kali. Dengan meminta pembuatan objek sinkronisasi pribadi hanya untuk mengunci, Anda pada dasarnya telah menggandakan ukuran objek dan menggandakan jumlah alokasi. Ketika masalah kinerja, ini adalah keuntungan.

Ketika Anda tidak peduli dengan jumlah alokasi atau jejak memori, menghindari kunci (ini) lebih disukai karena alasan yang ditunjukkan dalam jawaban lain.

zumalifeguard
sumber
-1

Akan ada masalah jika instance dapat diakses secara publik karena mungkin ada permintaan lain yang mungkin menggunakan instance objek yang sama. Lebih baik menggunakan variabel pribadi / statis.

William
sumber
5
Tidak yakin apa yang menambah pria itu, jawaban rinci sudah ada yang mengatakan hal yang sama.
Andrew Barber