2 Stimmen

Threadsicherer Lazy Initializer; ist das Austauschen von Func<>s eine gute Idee?

Die Klasse unten ist eine Implementierung eines schnellen, thread-sicheren, "lock free", lazy initializer.

Ich sage "lock free", obwohl es nicht vollständig ist; Es verwendet eine Sperre, bis es initialisiert wird, und ersetzt dann den Aufruf, um die Daten mit einer, die nicht die Sperre verwenden. (Ähnlich wie eine doppelt geprüfte Sperre mit einem flüchtigen Mitglied; und aus Leistungstests erhalte ich v. ähnliche Ergebnisse.)

Meine Frage ist: Ist dies eine gute/sichere Sache, die man tun sollte?

(Nachdem ich dies geschrieben hatte, bemerkte ich, dass .net 4.0 eine LazyInit<T> die dieselben Operationen und noch mehr ausführt, aber in einem sehr konstruierten Beispiel, das ich erstellt habe, war meine Implementierung etwas schneller :-) )

NB Die Klasse wurde geändert und umfasst nun Value Mitglied, und flüchtig auf dem _get

class ThreadSafeInitializer<TOutput>
    where TOutput : class
{
    readonly object _sync = new object();
    TOutput _output;
    private volatile Func<TOutput> _get;

    public TOutput Value { get { return _get(); } }

    public ThreadSafeInitializer(Func<TOutput> create)
    {
        _get = () =>
        {
            lock (_sync)
            {
                if (_output == null)
                {
                    _output = create();
                    _get = () => _output; // replace the Get method, so no longer need to lock
                }
                return _output;
            }
        };
    }
}

2voto

Sam Saffron Punkte 124121

Wie Guffa schon sagte, müssen Sie bei Get volatile hinzufügen, um die Sicherheit zu gewährleisten.

Allerdings fand ich die Umsetzung ein wenig schwierig zu verfolgen. Ich denke, es versucht, ein bisschen zu ausgefallen für sein eigenes Wohl zu sein.

Etwas in dieser Richtung ist also leichter zu verstehen:

class Lazy<T> {

    readonly object sync = new object();
    Func<T> init;
    T result;
    volatile bool initialized;

    public Lazy(Func<T> func) {
        this.init = func;
    }

    public T Value {
        get {

            if(initialized) return result;

            lock (sync) {
                if (!initialized) {
                    result = this.init();
                    initialized = true;
                }
            }
            return result;
        }
    }
}

Geringe Vorteile:

  • Weniger ausgefallen
  • Keine Leistungseinbußen beim Aufrufen der Func
  • Sperre wird nur bei der Initialisierung erworben.
  • Der Zugriff auf Value über obj.Value ist ein wenig intuitiver als obj.Get()

2voto

csharptest.net Punkte 58070

Ich schreibe diesen Code sehr oft:

private SomeThing _myThing = null;
public SomeThing MyThing 
{ get { return _myThing != null ? _myThing : _myThing = GetSomeThing(); } }

Lazy lädt einwandfrei und ist sehr gut lesbar. Mein Punkt? das Fehlen von lock(object) ist nicht zufällig. Lazy loading Eigenschaften mit Sperren wäre für Probleme IMO fragen.

Die Verwendung der Lock-Anweisung sollte streng kontrolliert werden, und sowohl Paul als auch Sam werden hiermit beschuldigt, Code aufzurufen, den sie nicht geschrieben haben, während sie eine Sperre halten. Vielleicht ist das keine große Sache, vielleicht blockiert es Ihr Programm. Und Sie tun dies innerhalb einer unschuldig aussehenden Eigenschaft, so dass Sie puede ein paar Millisekunden sparen?

Meiner Meinung nach wäre es besser, wenn Sie die Waffe zweimal sicher laden können, ohne dass es zu katastrophalen Folgen kommt. In dem seltenen Fall, dass zwei Threads gleichzeitig auf dieselbe Eigenschaft zugreifen, werden dann zwei Instanzen geladen... Es hängt davon ab, was Sie laden, ob das wirklich eine schlechte Sache ist. Ich wette, dass es in den meisten Fällen keine Rolle spielt, dass jeder Thread eine andere Instanz erhält. Wenn es eine Rolle spielt, würde ich empfehlen, die erforderlichen Sperren bei der Konstruktion durchzuführen und sich nicht um das faule Laden zu kümmern. Wahrscheinlich, da der Konstruktor nur auf einem Thread geschehen kann, benötigen Sie eine Sperre überhaupt nicht.

1voto

Guffa Punkte 663241

Machen Sie die Get Mitglied flüchtig, so dass Sie sicher sein können, dass Sie die aktuelle Version aufrufen. Mit dieser Änderung ist es sicher, soweit ich das beurteilen kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X