Ini adalah bentuk yang buruk untuk digunakan this
dalam 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 this
melanggar enkapsulasi dengan memaparkan sebagian dari implementasi penguncian Anda kepada publik. Juga tidak jelas bahwa Anda akan mendapatkan kunci this
kecuali 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 lock
hanya 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 lock
pernyataan, karena mereka tidak dapat diubah dan dibagikan / diakses di seluruh bagian aplikasi. Anda harus menggunakan variabel pribadi sebagai gantinya, sebuah Object
instance 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'.
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 mengimplementasikanToArray
atauToList
metode miliknya sendiri ...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 benarKarena jika orang bisa mendapatkan
this
pointer objek Anda (yaitu: Anda ), maka mereka juga dapat mencoba untuk mengunci objek yang sama. Sekarang mereka mungkin tidak menyadari bahwa Anda mengunci secarathis
internal, 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.sumber
Lihatlah Sinkronisasi Topik MSDN (Panduan Pemrograman C #)
sumber
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 daripadalock(this)
. Setelah mengatakan itu; pujian tulus kepada Alan karena menunjukkan bahwa itulock(typeof(SomeObject))
adalah praktik yang buruk.Sebuah instance dari
System.Type
adalah 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.
sumber
... dan argumen yang sama persis berlaku untuk konstruk ini juga:
sumber
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 yanglock(this)
sebenarnya terjadi adalah jauh lebih baik daripada itulock(typeof(SomeObject))
.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 memilikiprivate
,protected
atauinternal
kelas, Anda sudah bisa mengendalikan siapa yang mengunci objek Anda , karena Anda yakin telah menulis kode sendiri. Jadi pesannya di sini adalah: jangan memaparkannya sebagaipublic
. 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();
sumber
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:
Buat kelas baru seperti di bawah ini.
Berikut ini adalah program penguncian ini .
Berikut ini adalah program penguncian di myLock .
sumber
Random rand = new Random();
nvm saya pikir saya melihat itu saldo berulangAda 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:
sumber
Ada juga beberapa diskusi bagus tentang ini di sini: Apakah ini penggunaan yang tepat dari sebuah mutex?
sumber
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.
Untuk menyiasatinya, orang ini menggunakan Thread.TryMonitor (dengan waktu habis) alih-alih mengunci:
https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks
sumber
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/DMrU5hKarena 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.
sumber
Maaf teman-teman tapi saya tidak setuju dengan argumen bahwa mengunci ini dapat menyebabkan kebuntuan. Anda membingungkan dua hal: jalan buntu dan kelaparan.
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 menggunakanlock(this)
ujung dalam kunci yang mengunci objek Anda, akhirnya akan berakhir pada kelaparan abadi;)sumber
lock(this)
- kode semacam ini benar-benar salah. Saya hanya berpikir menyebutnya kebuntuan sedikit kasar.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:
sumber
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)
Keluaran
Perhatikan bahwa Thread # 12 tidak pernah berakhir karena mati terkunci.
sumber
DoWorkUsingThisLock
utas kedua tidak perlu untuk menggambarkan masalah ini?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.
sumber
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.
sumber