PDA

View Full Version : [C#] Multithreading e utilizzo dei Lock. Delucidazioni?


nocturnia
19-01-2011, 16:26
Ciao ragazzi spiego il mio problema!

Ho una applicazione console in C# che opera in multithreading e per questo sono costretto ad utilizzare dei costrutti Lock(nomeoggetto) per lockare momentaneamente porzioni di metodi per evitare che siano accessibili contemporaneamente da più thread allo stesso tempo.

Per instanziare i costrutti di Lock ho operato in questo modo:
- Ho creato vari oggetti dichiarandoli private static (private static object nomeoggetto = new Object() )
- Nei metodi locko un determinato oggetto (ogni lock ha un suo oggetto diverso).

Mi sono però subito reso conto di un problema che accade sui Dictionary e sulle List.
Avendo oggetti di Lock differenti può capitare che se metodo A sta effettuando (se pur lockato) un enumerazione su una Lista ed arriva metodo B (lockato mediante un altro oggetto) e tenta di enumerare sulla stessa Lista, io mi ritrovo con la mia bella eccezione "Collection Was modified".

Quindi ho optato per un altra soluzione:
I metodi che lavorano sulla stessa Lista vengono lockati con oggetti uguali in modo che solo un metodo alla volta possa ottenere il token di lock e svolgere le operazioni al suo interno.

Ora la mia domanda è: quel che sto facendo ed il mio ragionamento sono esatti? O solitamente è buona abitudine lockare UN SOLO oggetto per classe ed utilizzare sempre quello in tutti i metodi??

Non so se mi sono spiegato bene. In tal caso proverò a spiegarmi meglio!

Grazie mille per l'attenzione!

banryu79
19-01-2011, 16:56
Ciao, non conosco C#, ma so che assomiglia molto a Java.
Invece di usare lock espliciti come stai facendo, non sarebbe meglio usare delle collezioni thread-safe? Lo chiedo perchè appunto in Java ci sono, mi stupirei se non ci fossero anche nel framework .NET...

tomminno
19-01-2011, 17:22
Se stai usando .Net 4 potresti trovare quello che ti serve in System.Collections.Concurrent

nocturnia
19-01-2011, 17:52
il fatto è che in questo modo mi tocca riscrivere tutto il codice per sostituire tutti i Dictionary e tutte le List dato che ho provato a fare una semplice sostituzione ma vedo che alcuni attributi di Dictionary non sono più gli stessi per ConcurrentDictionary

banryu79
19-01-2011, 22:15
il fatto è che in questo modo mi tocca riscrivere tutto il codice per sostituire tutti i Dictionary e tutte le List dato che ho provato a fare una semplice sostituzione ma vedo che alcuni attributi di Dictionary non sono più gli stessi per ConcurrentDictionary
Puoi fare quello (dopo aver esaminato bene le classi concorrenti per conoscerle) oppure smazzarti con i lock espliciti, tecnica secondo me più laboriosa ed error-prone (beh, dipende dal livello di granularità della sincronizzazione che uno vuole ottenere; la cosa meno complicata è costringere i thread su un unico lock da aqcuisire e mantenere per tutta la durata di una chiamata ad un metodo, sia in lettura che in scrittura sulla collezione).
Certo, poi dipende da quanto è complesso quello che devi fare.

Per le collezioni ci sarebbe da dire che se vincoli tutti i clienti che operano (per esempio) su una lista a possedere il lock su un singolo oggetto per tutto il tempo della chiamata ad un qualsiasi metodo (come ho detto prima) riesci certamente a garantire la sincronicità degli accessi da parte di più thread, ma solo un thread alla volta può accedere alla lista.

Invece, è probabile che le collezioni concorrenti messe a disposizione dal framework .NET siano molto più performanti, perche basate su meccanismi di sincronizzazione molto più granulari (almeno nel JDK Java è così).

Kralizek
20-01-2011, 08:45
Se puoi fare una chiara distinzione tra chi scrive e chi legge, allora puoi usare il Readerwriterlock. Così chi legge non locka, ma chi scrive si (è un filo più complessa di così, leggi doc)

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

banryu79
20-01-2011, 09:36
Se puoi fare una chiara distinzione tra chi scrive e chi legge, allora puoi usare il Readerwriterlock. Così chi legge non locka, ma chi scrive si (è un filo più complessa di così, leggi doc)

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx
Sembra che quel tipo di lock permetta di condividere una risorsa in sola lettura da più thread contemporaneamente, mentre in scrittura da un solo thread, e solo se non ci sono altri thread che hanno il lock in lettura.
Da il meglio se (in termini di prestazioni) sulla collezione condivisa ci sono frequenti letture, con sporadiche e veloci scritture.

nocturnia
20-01-2011, 10:10
Grazie mille a tutti per l'aiuto, mi sto orientando verso System.Collections.Concurrent che la vedo veramente ottima per quel che mi serve inquanto le operazioni di lettura sono si frequenti ma lo sono altrettanto (e da diversi thread) quelle di scrittura!

Sto iniziando quindi a riprogrammare i miei vecchi Dictionary in ConcurrentDictionary ma mi sono bloccato nel momento in cui mi serviva enumerare gli elementi dell'oggetto dictionary.

Con il vecchio Dictionary avrei fatto:

Dictionary<tKey, tValue>.Enumerator ecc..

ma ora con ConcurrentDictionary non riesco a trovare niente :help:

banryu79
20-01-2011, 10:29
Qui: http://msdn.microsoft.com/it-it/library/system.collections.dictionarybase.aspx puoi leggere che:

...
L'enumerazione di un insieme non è di per sé una procedura thread-safe. Anche se un insieme è sincronizzato, è possibile che venga modificato da altri thread, con conseguente generazione di un'eccezione da parte dell'enumeratore. Per garantire la protezione dei thread durante l'enumerazione, è possibile bloccare l'insieme per l'intera enumerazione oppure intercettare le eccezioni determinate dalle modifiche apportate da altri thread.


Invece qui: http://msdn.microsoft.com/it-it/library/dd287152.aspx puoi consultare la sezione "Metodi di estensione" e cercare un metodo che faccia al caso tuo (mi pare di capire che sono esposti dei metodi che enumerano il dizionario implicitamente, eseguendo per ogni elemento un'azione specificata dal chiamante).

A quale scopo enumeri il dizionario?
Vedi quindi se uno di quei metodi fornisce un servizio che ti permette di ottenerlo senza dover gestire esplicitamente una enumerazione sul dizionario.

Di più non so aiutarti: non conosco C# e .NET

nocturnia
20-01-2011, 12:07
A quale scopo enumeri il dizionario?

enumero il dizionario per poterne poi scorrere in un while (mediante un movenext() ) gli elementi ed effettuarne dei controlli.

Comunque ti ringrazio provo a dare uno sguardo a quei metodi!

Kralizek
20-01-2011, 19:49
un paio di consigli:

1) "salva" il contenuto della collezione in un array con "ToArray()"
2) prova a contenere tutta la logica che deve essere thread safe in un solo posto, così se devi cambiare meccanismo, ti è più facile.

var dict = new System.Collections.Concurrent.ConcurrentDictionary<int, string>();
dict.AddOrUpdate(1, "0", (k,ov) => "Hello world");
dict.AddOrUpdate(1, "0", (k,ov) => "Hello world");

var array = dict.ToArray();