Saya memahami bahwa secara umum List bukanlah thread safe, namun apakah ada yang salah dengan menambahkan item ke dalam daftar jika thread tidak pernah melakukan operasi lain di daftar (seperti melewatinya)?
Contoh:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Jawaban:
Di balik layar banyak hal terjadi, termasuk mengalokasikan ulang buffer dan menyalin elemen. Kode itu akan menyebabkan bahaya. Sederhananya, tidak ada operasi atomik saat menambahkan ke daftar, setidaknya properti "Panjang" perlu diperbarui, dan item perlu diletakkan di lokasi yang tepat, dan (jika ada variabel terpisah) yang dibutuhkan indeks untuk diperbarui. Beberapa utas dapat saling menginjak-injak. Dan jika pertumbuhan diperlukan maka masih banyak lagi yang terjadi. Jika ada sesuatu yang menulis ke daftar, tidak ada hal lain yang harus dibaca atau ditulis di sana.
Di .NET 4.0 kami memiliki koleksi serentak, yang mudah digunakan untuk threadsafe dan tidak memerlukan kunci.
sumber
ConcurrentList
tipe bawaan. Ada tas, kamus, tumpukan, antrian, dll secara bersamaan, tetapi tidak ada daftar.Pendekatan Anda saat ini tidak thread-safe - Saya sarankan untuk menghindari ini sama sekali - karena pada dasarnya Anda melakukan transformasi data PLINQ mungkin merupakan pendekatan yang lebih baik (saya tahu ini adalah contoh yang disederhanakan tetapi pada akhirnya Anda memproyeksikan setiap transaksi ke "status" lain "objek).
List<object> list = transactions.AsParallel() .Select( tran => new object()) .ToList();
sumber
Parallel.Foreach
beban berlebih yang mengambil indeks - dalam hal ini setiap utas memanipulasi entri larik yang berbeda dan Anda harus aman.Bukan hal yang tidak masuk akal untuk bertanya. Ada yang kasus di mana metode yang dapat menyebabkan masalah benang-keselamatan dalam kombinasi dengan metode lain yang aman jika mereka adalah satu-satunya metode yang disebut.
Namun, ini jelas bukan masalahnya, ketika Anda mempertimbangkan kode yang ditampilkan di reflektor:
public void Add(T item) { if (this._size == this._items.Length) { this.EnsureCapacity(this._size + 1); } this._items[this._size++] = item; this._version++; }
Bahkan jika
EnsureCapacity
itu sendiri threadsafe (dan tentu saja tidak), kode di atas jelas tidak akan menjadi threadsafe, mengingat kemungkinan panggilan simultan ke operator tambahan menyebabkan kesalahan penulisan.Kunci, gunakan ConcurrentList, atau mungkin gunakan antrean bebas kunci sebagai tempat menulis beberapa utas, dan baca darinya - baik secara langsung atau dengan mengisi daftar dengannya - setelah mereka menyelesaikan pekerjaan mereka (saya berasumsi bahwa beberapa penulisan simultan diikuti dengan pembacaan utas tunggal adalah pola Anda di sini, dilihat dari pertanyaan Anda, karena jika tidak, saya tidak dapat melihat bagaimana kondisi di mana
Add
satu-satunya metode yang dipanggil dapat digunakan).sumber
Jika Anda ingin menggunakan
List.add
dari beberapa utas dan tidak peduli tentang pengurutan, maka Anda mungkin tidak memerlukan kemampuan pengindeksan aList
, dan harus menggunakan beberapa koleksi bersamaan yang tersedia.Jika Anda mengabaikan saran ini dan hanya melakukannya
add
, Anda dapat membuatadd
utas aman tetapi dalam urutan yang tidak dapat diprediksi seperti ini:private Object someListLock = new Object(); // only once ... lock (someListLock) { someList.Add(item); }
Jika Anda menerima pemesanan yang tidak dapat diprediksi ini, kemungkinan besar Anda seperti yang disebutkan sebelumnya tidak memerlukan koleksi yang dapat melakukan indexing seperti pada
someList[i]
.sumber
Ini akan menyebabkan masalah, karena List dibuat di atas larik dan tidak aman untuk thread, Anda mungkin mendapatkan pengecualian indeks di luar batas atau beberapa nilai menimpa nilai lain, tergantung di mana utasnya. Pada dasarnya, jangan lakukan itu.
Ada beberapa potensi masalah ... Jangan. Jika Anda memerlukan koleksi thread yang aman, gunakan kunci atau salah satu dari koleksi System.Collections.Concurrent.
sumber
Saya memecahkan masalah saya menggunakan
ConcurrentBag<T>
alih-alihList<T>
seperti ini:ConcurrentBag<object> list = new ConcurrentBag<object>(); Parallel.ForEach(transactions, tran => { list.Add(new object()); });
sumber
Jawaban singkatnya: ya.
Jawaban panjang: jalankan program di bawah ini.
using System; using System.Collections.Generic; using System.Linq; using System.Threading; class Program { readonly List<int> l = new List<int>(); const int amount = 1000; int toFinish = amount; readonly AutoResetEvent are = new AutoResetEvent(false); static void Main() { new Program().Run(); } void Run() { for (int i = 0; i < amount; i++) new Thread(AddTol).Start(i); are.WaitOne(); if (l.Count != amount || l.Distinct().Count() != amount || l.Min() < 0 || l.Max() >= amount) throw new Exception("omg corrupted data"); Console.WriteLine("All good"); Console.ReadKey(); } void AddTol(object o) { // uncomment to fix // lock (l) l.Add((int)o); int i = Interlocked.Decrement(ref toFinish); if (i == 0) are.Set(); } }
sumber
Seperti yang telah dikatakan orang lain, Anda dapat menggunakan koleksi bersamaan dari
System.Collections.Concurrent
namespace. Jika Anda dapat menggunakan salah satunya, ini lebih disukai.Tetapi jika Anda benar-benar menginginkan daftar yang baru saja disinkronkan, Anda dapat melihat
SynchronizedCollection<T>
-Class inSystem.Collections.Generic
.Perhatikan bahwa Anda harus menyertakan rakitan System.ServiceModel, yang juga merupakan alasan mengapa saya tidak begitu menyukainya. Tapi terkadang saya menggunakannya.
sumber
Bahkan menambahkan elemen pada utas yang berbeda tidak aman untuk utas.
Di C # 4.0 ada koleksi serentak (lihat http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html ).
sumber