Possibile perdita di memoria in ConcurrentBag?

Ho letto le nuove collezioni simultanee e soprattutto la ConcurrentBag ha attirato la mia attenzione. Poiché ConcurrentBag contiene internamente un set locale su ogni singolo thread che lo utilizza per tenere traccia degli elementi, ciò significa che quando il thread stesso esce dall’ambito, verrà comunque fatto riferimento in memoria da ConcurrentBag. Ciò a sua volta significa sia la memoria rivendicata dal thread, sia le risorse native? (scusami se non conosco il funzionamento interno esatto dell’object thread .NET)

Posso assumere un caso in cui si dispone di 1 ConcurrentBack globale per un servizio web multithread in cui si hanno molti client che aggiungono attività. Queste attività vengono aggiunte dai thread sul threadpool. Ora il threadpool è un modo molto efficiente per gestire i thread ma rimuove e crea i thread in base alla quantità di lavoro. Pertanto, un tale servizio web può a volte trovarsi nei guai poiché il sacchetto sottostante fa ancora riferimento a molti thread dovrebbero essere distrutti.

Ho creato un’app rapida per testare questo comportamento:

static ConcurrentBag bag = new ConcurrentBag(); static void FillBag() { for (int i = 0; i  { FillBag(); PrintState(); }); // empty bag PrintState(); // first 100 items are added on main thread FillBag(); PrintState(); // second 100 items are added on remote thread remote.Start(); remote.Join(); // since the remote thread is gone out of scope, what happened to its local storage which is part of the bag? PrintState(); // now force a cleanup WeakReference weakRemoteReference = new WeakReference(remote); remote = null; GC.Collect(); GC.WaitForPendingFinalizers(); // Now check if the thread still exists if (weakRemoteReference.IsAlive) Console.WriteLine("Remote thread still exists"); PrintState(); Console.ReadLine(); 

E l’output conferma la mia storia:

 Bag size is: 0 Bag size is: 100 Bag size is: 200 Bag size is: 200 Remote thread still exists Bag size is: 200 

È prevedibile questo comportamento, ho fatto un errore nel mio test o questo può essere considerato un difetto di progettazione?

La ConcurrentBag mantiene effettivamente le cose nella memoria locale del thread e se si abbandonano i thread può causare una perdita di memoria. Tuttavia, l’implementazione è in grado di “rubare” elementi dalla lista di un thread per dare ad un altro thread. Puoi vedere questo in azione se scrivi quanto segue:

 ConcurrentBag MyBag = new ConcurrentBag(); void DoIt() { for (int i = 0; i < 10; ++i) { MyBag.Add(i); } ThreadPool.QueueUserWorkItem(EmptyBag); Console.Write("Press Enter:"); Console.ReadLine(); Console.WriteLine("{0} items in bag", MyBag.Count); } void EmptyBag(object state) { int take; while (MyBag.TryTake(out take)) { Console.WriteLine(take); } Console.WriteLine("Bag is empty"); } 

Se esegui quel programma e attendi fino a quando il messaggio "La borsa è vuota" prima di premere Invio, vedrai che la borsa viene effettivamente svuotata.

Quindi, a patto che ci sia un thread che legge dalla borsa, alla fine verrà svuotato. Anche se tutti gli elementi sono stati aggiunti da altri thread.

Quindi, sì, c'è una ansible perdita di memoria. In pratica, però, se più thread stanno accedendo al bagaglio, è probabile che non sia un problema.