Saya dapat menerapkan Kamus aman-utas di C # dengan mengambil dari IDictionary dan mendefinisikan objek SyncRoot pribadi:
public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
private readonly object syncRoot = new object();
private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
public object SyncRoot
{
get { return syncRoot; }
}
public void Add(TKey key, TValue value)
{
lock (syncRoot)
{
d.Add(key, value);
}
}
// more IDictionary members...
}
Saya kemudian mengunci objek SyncRoot ini di seluruh konsumen saya (banyak utas):
Contoh:
lock (m_MySharedDictionary.SyncRoot)
{
m_MySharedDictionary.Add(...);
}
Saya bisa membuatnya berhasil, tetapi ini menghasilkan beberapa kode yang jelek. Pertanyaan saya adalah, apakah ada cara yang lebih baik dan lebih elegan untuk mengimplementasikan Dictionary thread-safe?
Jawaban:
Seperti yang dikatakan Peter, Anda dapat merangkum semua keamanan thread di dalam kelas. Anda harus berhati-hati dengan peristiwa apa pun yang Anda ungkapkan atau tambahkan, pastikan bahwa peristiwa tersebut dipanggil di luar kunci apa pun.
Edit: Dokumen MSDN menunjukkan bahwa enumerasi secara inheren tidak aman untuk thread. Itu bisa menjadi salah satu alasan untuk mengekspos objek sinkronisasi di luar kelas Anda. Cara lain untuk mendekatinya adalah dengan menyediakan beberapa metode untuk melakukan tindakan pada semua anggota dan mengunci sekitar pencacahan anggota. Masalahnya adalah Anda tidak tahu apakah tindakan yang diteruskan ke fungsi itu memanggil beberapa anggota kamus Anda (yang akan mengakibatkan kebuntuan). Mengekspos objek sinkronisasi memungkinkan konsumen membuat keputusan tersebut dan tidak menyembunyikan kebuntuan di dalam kelas Anda.
sumber
Kelas .NET 4.0 yang mendukung konkurensi dinamai
ConcurrentDictionary
.sumber
Mencoba menyinkronkan secara internal hampir pasti tidak cukup karena ini pada tingkat abstraksi yang terlalu rendah. Katakanlah Anda membuat operasi
Add
dan satuContainsKey
per satu thread-safe sebagai berikut:Lalu apa yang terjadi ketika Anda menyebut bit kode yang seharusnya aman untuk thread ini dari beberapa thread? Apakah akan selalu berhasil?
Jawaban sederhananya adalah tidak. Pada titik tertentu,
Add
metode ini akan menampilkan pengecualian yang menunjukkan bahwa kunci tersebut sudah ada di kamus. Bagaimana ini bisa terjadi dengan kamus aman utas, Anda mungkin bertanya? Hanya karena setiap operasi aman untuk thread, kombinasi dua operasi tidak, karena thread lain dapat memodifikasinya antara panggilan Anda keContainsKey
danAdd
.Yang berarti untuk menulis skenario jenis ini dengan benar, Anda memerlukan kunci di luar kamus, mis
Tapi sekarang, mengingat Anda harus menulis kode penguncian eksternal, Anda mencampur sinkronisasi internal dan eksternal, yang selalu menyebabkan masalah seperti kode yang tidak jelas dan kebuntuan. Jadi pada akhirnya Anda mungkin lebih baik:
Gunakan normal
Dictionary<TKey, TValue>
dan sinkronkan secara eksternal, menyertakan operasi gabungan di atasnya, atauTulis pembungkus thread-safe baru dengan antarmuka berbeda (yaitu tidak
IDictionary<T>
) yang menggabungkan operasi sepertiAddIfNotContained
metode sehingga Anda tidak perlu menggabungkan operasi darinya.(Saya cenderung memilih # 1 sendiri)
sumber
Anda tidak boleh mempublikasikan objek kunci pribadi Anda melalui properti. Objek kunci harus ada secara pribadi dengan tujuan bertindak sebagai titik pertemuan.
Jika kinerja terbukti buruk dengan menggunakan kunci standar, maka koleksi kunci Wintellect Power Threading dapat sangat berguna.
sumber
Ada beberapa masalah dengan metode implementasi yang Anda gambarkan.
Secara pribadi, saya telah menemukan cara terbaik untuk menerapkan kelas aman utas adalah melalui kekekalan. Ini benar-benar mengurangi jumlah masalah yang dapat Anda hadapi dengan keamanan utas. Lihat Blog Eric Lippert untuk lebih jelasnya.
sumber
Anda tidak perlu mengunci properti SyncRoot di objek konsumen Anda. Kunci yang Anda miliki dalam metode kamus sudah cukup.
Untuk Menguraikan: Apa yang akhirnya terjadi adalah kamus Anda dikunci untuk jangka waktu yang lebih lama dari yang diperlukan.
Yang terjadi dalam kasus Anda adalah sebagai berikut:
Katakanlah utas A memperoleh kunci pada SyncRoot sebelumnya panggilan ke m_mySharedDictionary.Add. Thread B kemudian mencoba mendapatkan kunci tetapi diblokir. Faktanya, semua utas lainnya diblokir. Thread A diizinkan untuk memanggil ke metode Add. Pada pernyataan kunci dalam metode Tambah, utas A diizinkan untuk mendapatkan kunci lagi karena sudah memiliki itu. Setelah keluar dari konteks kunci di dalam metode dan kemudian di luar metode, utas A telah melepaskan semua kunci yang memungkinkan utas lain untuk melanjutkan.
Anda cukup mengizinkan konsumen mana pun untuk memanggil ke metode Tambah karena pernyataan kunci dalam metode Add kelas SharedDictionary Anda akan memiliki efek yang sama. Pada saat ini, Anda memiliki penguncian yang berlebihan. Anda hanya akan mengunci SyncRoot di luar salah satu metode kamus jika Anda harus melakukan dua operasi pada objek kamus yang perlu dijamin untuk terjadi secara berurutan.
sumber
Hanya berpikir mengapa tidak membuat ulang kamus? Jika membaca banyak tulisan maka penguncian akan menyinkronkan semua permintaan.
contoh
sumber
Koleksi Dan Sinkronisasi
sumber