Saya memiliki kelas berikut.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Saya perlu mengubah bidang "Data" dari utas yang berbeda, jadi saya ingin beberapa pendapat tentang implementasi utas saat ini yang aman.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
Apakah ada solusi yang lebih baik, langsung ke lapangan dan melindunginya dari akses bersamaan dengan beberapa utas?
System.Collections.Concurrent
ReaderWriterLock
akan membantu (efisien) ketika banyak pembaca dan satu penulis. Kita harus tahu apakah ini kasus OPJawaban:
Implementasi Anda benar. Sayangnya, NET Framework. Tidak menyediakan tipe hashset bersamaan built-in. Namun, ada beberapa solusi.
ConcurrentDictionary (disarankan)
Yang pertama ini menggunakan kelas
ConcurrentDictionary<TKey, TValue>
di namespaceSystem.Collections.Concurrent
. Dalam kasus ini, nilainya tidak ada gunanya, jadi kita bisa menggunakan yang sederhanabyte
(1 byte dalam memori).Ini adalah opsi yang disarankan karena jenisnya aman untuk thread dan memberi Anda keuntungan yang sama daripada
HashSet<T>
kunci kecuali dan nilai adalah objek yang berbeda.Sumber: MSDN sosial
ConcurrentBag
Jika Anda tidak keberatan dengan entri duplikat, Anda bisa menggunakan kelas
ConcurrentBag<T>
di namespace yang sama dengan kelas sebelumnya.Implementasi diri
Akhirnya, seperti yang Anda lakukan, Anda bisa mengimplementasikan tipe data Anda sendiri, menggunakan kunci atau cara lain yang disediakan oleh .NET agar Anda aman dari thread. Berikut adalah contoh yang bagus: Bagaimana mengimplementasikan ConcurrentHashSet di .Net
Satu-satunya kelemahan dari solusi ini adalah bahwa jenisnya
HashSet<T>
tidak secara bersamaan mengakses, bahkan untuk operasi membaca.Saya mengutip kode dari posting terkait (aslinya ditulis oleh Ben Mosher ).
EDIT: Pindahkan metode kunci pintu masuk
try
blok, karena mereka bisa melempar pengecualian dan menjalankan instruksi yang terkandung dalamfinally
blok.sumber
null
referensi (referensi membutuhkan 4 byte dalam runtime 32-bit dan 8 byte dalam runtime 64-bit). Oleh karena itu, menggunakan abyte
, struct kosong, atau sejenisnya dapat mengurangi jejak memori (atau mungkin tidak jika runtime menyelaraskan data pada batas memori asli untuk akses yang lebih cepat).Alih-alih membungkus
ConcurrentDictionary
atau mengunciHashSet
saya membuatConcurrentHashSet
berdasarkan aktualConcurrentDictionary
.Implementasi ini mendukung operasi dasar per item tanpa
HashSet
operasi yang ditetapkan karena kurang masuk akal dalam skenario bersamaan IMO:Output: 2
Anda bisa mendapatkannya dari NuGet di sini dan lihat sumbernya di GitHub di sini .
sumber
ISet<T>
antarmuka yang benar-benar cocok denganHashSet<T>
semantik?Overlaps
misalnya akan perlu mengunci instance selama menjalankannya, atau memberikan jawaban yang mungkin sudah salah. Kedua opsi tersebut adalah IMO yang buruk (dan dapat ditambahkan secara eksternal oleh konsumen).Karena tidak ada orang lain yang menyebutkannya, saya akan menawarkan pendekatan alternatif yang mungkin atau mungkin tidak sesuai untuk tujuan khusus Anda:
Microsoft Immutable Collections
Dari posting blog oleh tim MS di belakang:
Koleksi ini termasuk ImmutableHashSet <T> dan ImmutableList <T> .
Performa
Karena koleksi abadi menggunakan struktur data pohon di bawahnya untuk memungkinkan pembagian struktural, karakteristik kinerjanya berbeda dari koleksi yang bisa berubah. Ketika membandingkan dengan koleksi penguncian yang dapat diubah, hasilnya akan tergantung pada pertikaian kunci dan pola akses. Namun, diambil dari posting blog lain tentang koleksi yang tidak dapat diubah:
Dengan kata lain, dalam banyak kasus perbedaannya tidak akan terlihat dan Anda harus pergi dengan pilihan yang lebih sederhana - yang untuk set bersamaan akan digunakan
ImmutableHashSet<T>
, karena Anda tidak memiliki implementasi penguncian yang bisa berubah yang ada! :-)sumber
ImmutableHashSet<T>
tidak banyak membantu jika maksud Anda adalah untuk memperbarui keadaan bersama dari beberapa utas atau apakah saya melewatkan sesuatu di sini?ImmutableInterlocked.Update
tampaknya merupakan tautan yang hilang. Terima kasih!Bagian rumit tentang membuat
ISet<T>
konkuren adalah bahwa metode yang ditetapkan (persatuan, persimpangan, perbedaan) bersifat iteratif. Paling tidak Anda harus mengulangi semua anggota dari salah satu set yang terlibat dalam operasi, sambil mengunci kedua set.Anda kehilangan keuntungan
ConcurrentDictionary<T,byte>
ketika Anda harus mengunci seluruh set selama iterasi. Tanpa penguncian, operasi ini tidak aman untuk thread.Mengingat tambahan overhead
ConcurrentDictionary<T,byte>
, mungkin lebih bijaksana hanya menggunakan bobot yang lebih ringanHashSet<T>
dan hanya mengelilingi segala sesuatu di kunci.Jika Anda tidak memerlukan operasi yang ditetapkan, gunakan
ConcurrentDictionary<T,byte>
dan gunakan sajadefault(byte)
sebagai nilai saat Anda menambahkan kunci.sumber
Saya lebih suka solusi lengkap jadi saya melakukan ini: Pikirkan Anda, hitung saya diimplementasikan dengan cara yang berbeda karena saya tidak mengerti mengapa orang dilarang membaca hashset ketika mencoba menghitung nilainya.
@ Zen, Terima kasih sudah memulainya.
sumber
EnterWriteLock
, mengapaEnterReadLock
ada? Tidak bisakah kunci baca digunakan untuk metode sepertiContains
?