Le raccolte concorrenti, i metodi di lettura dovrebbero consentire più thread contemporaneamente?

2

Ho una raccolta personalizzata e voglio aggiungere un wrapper per consentire l'accesso simultaneo.

public class MyConcurrentCollection<T>
{
    private MyCollection _collection; // passed in constructor

    public void Add(T item)
    {
        //modifies and may temporarily "break" the collection while executing this method
    }

    public bool Contains(T item)
    {
        //only reads
    }

    // other read and write methods
}

In questo momento, ho object membro privato che funge da blocco in ogni metodo, consentendo solo un thread alla volta di accedere alla raccolta, quindi ogni metodo ha il seguente aspetto:

public bool Contains(T item)
{
    lock(_lock)
    {
        return _collection.Contains(item);
    }
}

Tuttavia questo sembra davvero inefficiente. Poiché Contains() legge solo dalla raccolta, dovrei consentire più thread in esso?

Ovviamente, ho bisogno di bloccare l'accesso a Add() e ad altri metodi mentre ci sono thread in Contains() e ho bisogno di bloccare l'accesso a Contains() se c'è un thread che desidera modificare la collezione.

C'è qualche svantaggio nel consentire l'uso di più thread in metodi di sola lettura, o dovrei restare con la mia soluzione di base?

    
posta isklenar 25.02.2015 - 11:13
fonte

3 risposte

1

È inefficiente ma è necessario - cosa succede se leggi dalla raccolta mentre qualcun altro sta aggiungendo una nuova voce ma, nel modo di fare il thread, non ha ancora finito di scrivere i dati della nuova voce?

Ci sono modi per renderlo più efficiente, in particolare utilizzando un blocco di lettura-scrittura, che blocca l'intera raccolta sia per i lettori che per gli autori se qualcuno sta scrivendo, ma consente l'accesso a più lettori (ad esempio un blocco di lettura impedisce a uno scrittore: lo scrittore deve aspettare fino a quando non hai finito di leggere, ma non blocca altri lettori).

Per inciso, l'utilizzo di un oggetto come blocco non è considerato una buona pratica. Utilizzare un costrutto di blocco dedicato. Ricordo di aver letto da qualche parte il team CLR che non avrebbero mai permesso tale uso.

    
risposta data 25.02.2015 - 11:55
fonte
1

Se un thread chiama Add e un altro thread chiama Contains, esattamente alla stessa ora, e tutto viene implementato correttamente, quindi Contains restituirà il valore che era corretto prima di chiamare Add, o il valore che era corretto dopo aver chiamato Aggiungi . Questo è il meglio che possiamo aspettarci.

Se è possibile implementare sia il metodo Add che il metodo Contains in un modo che Contains restituirà uno di questi due risultati, senza utilizzare un metodo Lock in the contains, anche se è chiamato proprio nel mezzo di Add che fa la sua cosa , quindi stai bene. In genere ciò avviene aggiungendo la creazione di strutture di dati che non fanno ancora parte del contenitore e quindi collegando tutto nel contenitore utilizzando una singola operazione atomica.

    
risposta data 27.03.2015 - 14:54
fonte
0

Puoi farlo senza blocchi, ma il costo è che il tuo metodo di aggiunta sarà più costoso. Se hai molto più letture rispetto alle scritture, il tuo metodo add può semplicemente creare un oggetto MyCollection nuovo e aggiornato:

public void Add(T item)
{
    MyCollection newCollection = new MyCollection();
    newCollection.Append(_collection); // fill with old values
    newCollection.Add(item); // add the new item
    _collection = newCollection; // switch the internal pointer to the new object
}

Questo funziona perché i thread che accedono al metodo di lettura vedono solo il vecchio o il nuovo oggetto, quindi la raccolta non è mai in uno stato intermedio per altri thread. Se hai più di un thread di scrittura devi ovviamente aggiungere un blocco al metodo add (ma non ai metodi di lettura).

Nonostante tutto questo, dovresti usare le raccolte da system.collections.concurrent se possibile, perché sono molto meno soggette a errori.

    
risposta data 25.02.2015 - 12:47
fonte

Leggi altre domande sui tag