2009-11-11 3 views
66

Ich habe eine Klasse und viele Komponententests geschrieben, aber ich habe es nicht threadsicher gemacht. Nun möchte ich den Klassen-Thread sicher machen, aber um es zu beweisen und TDD zu verwenden, möchte ich einige fehlgeschlagene Komponententests schreiben, bevor ich mit dem Refactoring beginne.Gerätetest für Fadensicherheit?

Jeder gute Weg, dies zu tun?

Mein erster Gedanke ist nur ein paar Threads erstellen und sie alle die Klasse in einer unsicheren Weise verwenden. Tun Sie das genug oft mit genug Threads und ich bin verpflichtet zu sehen, dass es bricht.

+0

Ich habe Komponententests in der Weise durchgeführt, die Sie zuvor beschrieben haben, aber immer das Gefühl, dass es ein Element der Zufälligkeit im Ergebnis gibt, also werde ich die Antworten hier mit Interesse verfolgen: o) –

+3

Duplizieren: http: // stackoverflow.com/questions/1715822/unit-test-for-thread-safe-ness – JeffH

+12

@JeffH Ja, du hast das richtig gemacht. Pffffff, hör auf zu versuchen, ein Polizist zu sein. –

Antwort

21

Es gibt zwei Produkte, die Sie dort helfen können:

Beide Check für Deadlocks in Ihrem Code (über Unit-Test) und ich denke, Schach Kontrollen für Rennen Bedingungen auch.

Die Verwendung beider Tools ist einfach - Sie schreiben einen einfachen Komponententest und führen Ihren Code mehrmals aus und prüfen, ob Deadlocks/Race Conditions in Ihrem Code möglich sind.

Edit: Google hat ein Tool veröffentlicht, die für Race Condition in Runtime überprüft (nicht während der Tests), die thread-race-test genannt.
Es wird nicht alle Rennbedingungen finden, da es nur den aktuellen Lauf analysiert und nicht alle möglichen Szenarien wie das obige, aber es könnte Ihnen helfen, die Wettlaufsituation zu finden, sobald es passiert.

Aktualisierung: Typemock Website hatte keine Verbindung mehr zu Racer, und es wurde in den letzten 4 Jahren nicht aktualisiert. Ich denke, das Projekt wurde geschlossen.

+0

Ich denke, die MS Chess Linke ist kaputt. Versuchen Sie diese: http://research.microsoft.com/en-us/projects/chess/default.aspx – jpbochi

+1

Der Typemock Racer scheint auch gebrochen zu sein. Versuchen Sie Folgendes: http://site.typemock.com/typemock-racer – jpbochi

+0

Der MS Chess-Link scheint ab sofort zu funktionieren. Der Typemock Racer-Link wurde korrigiert. –

10

Das Problem ist, dass die meisten Multithreading-Probleme, wie Race Conditions, von ihrer Natur her nicht deterministisch sind. Sie können vom Hardware-Verhalten abhängen, das Sie möglicherweise nicht emulieren oder auslösen können.

Das bedeutet, auch wenn Sie Tests mit mehreren Threads durchführen, werden sie nicht konsistent ausfallen, wenn Sie einen Fehler in Ihrem Code haben.

+0

-1 für "... Hardware-Verhalten ... kann möglicherweise nicht emulieren ...". Es erscheint möglich, dass zumindest für die einfacheren Fälle die Anzahl der Interleaving-Zugriffskombinationen endlich ist und aufgezählt werden könnte (wenn auch "irgendwie") und dann der Code gezwungen wird, jede Kombination über einen instrumentierten Thread-Scheduler auszuüben. Einige Aussagen sagen, dass Schach 100% Deckung bietet. Wenn das stimmt, dann sollte Hardware kein Faktor sein. – crokusek

1

Sie einen Testfall für jede Gleichzeitigkeit Szenario von Bedeutung konstruieren müssen; dies erfordert eine effizienten Betrieb mit langsamen Äquivalenten (oder Mocks) und das Ausführen mehrere Tests in Schleifen zu ersetzen, die Möglichkeit von Streitigkeiten

ohne spezifische Testfälle zu erhöhen, ist es schwierig, spezifische Tests

einig potenziell nützliche Referenz vorzuschlagen Material:

3

Ich habe Leute gesehen, die versuchen, dies mit Standard-Unittests zu testen, wie Sie selbst vorschlagen. Die Tests sind langsam und konnten bislang kein einziges der Nebenläufigkeitsprobleme identifizieren, mit denen unser Unternehmen zu kämpfen hat.

Nach vielen Misserfolgen, und trotz meiner Liebe zu Unittests, habe ich akzeptiert, dass Fehler in Nebenläufigkeit keine Unittests Stärken sind. Normalerweise ermutige ich die Analyse und Überprüfung zugunsten von Unittests für Klassen, in denen Nebenläufigkeit ein Thema ist. Bei totalem Überblick des Systems ist es in vielen Fällen möglich, die Ansprüche der Fadensicherheit zu beweisen/zu verfälschen.

Jedenfalls würde ich gerne jemanden haben, der mir etwas gibt, was auf das Gegenteil hinweisen könnte, also schaue ich genau hin.

2

Als ich vor kurzem das gleiche Problem ansprechen musste, dachte ich darüber nach; Zuallererst hat Ihre bestehende Klasse eine Verantwortung und das ist eine gewisse Funktionalität. Es ist nicht die Aufgabe des Objekts, threadsicher zu sein. Wenn es Thread-sicher sein muss, sollte ein anderes Objekt verwendet werden, um diese Funktionalität bereitzustellen. Aber wenn ein anderes Objekt die Thread-Sicherheit bietet, kann es nicht optional sein, denn dann können Sie nicht beweisen, dass Ihr Code Thread-sicher ist. Also das ist, wie ich damit umgehen:

// This interface is optional, but is probably a good idea. 
public interface ImportantFacade 
{ 
    void ImportantMethodThatMustBeThreadSafe(); 
} 

// This class provides the thread safe-ness (see usage below). 
public class ImportantTransaction : IDisposable 
{ 
    public ImportantFacade Facade { get; private set; } 
    private readonly Lock _lock; 

    public ImportantTransaction(ImportantFacade facade, Lock aLock) 
    { 
     Facade = facade; 
     _lock = aLock; 
     _lock.Lock(); 
    } 

    public void Dispose() 
    { 
     _lock.Unlock(); 
    } 
} 

// I create a lock interface to be able to fake locks in my tests. 
public interface Lock 
{ 
    void Lock(); 
    void Unlock(); 
} 

// This is the implementation I want in my production code for Lock. 
public class LockWithMutex : Lock 
{ 
    private Mutex _mutex; 

    public LockWithMutex() 
    { 
     _mutex = new Mutex(false); 
    } 

    public void Lock() 
    { 
     _mutex.WaitOne(); 
    } 

    public void Unlock() 
    { 
     _mutex.ReleaseMutex(); 
    } 
} 

// This is the transaction provider. This one should replace all your 
// instances of ImportantImplementation in your code today. 
public class ImportantProvider<T> where T:Lock,new() 
{ 
    private ImportantFacade _facade; 
    private Lock _lock; 

    public ImportantProvider(ImportantFacade facade) 
    { 
     _facade = facade; 
     _lock = new T(); 
    } 

    public ImportantTransaction CreateTransaction() 
    { 
     return new ImportantTransaction(_facade, _lock); 
    } 
} 

// This is your old class. 
internal class ImportantImplementation : ImportantFacade 
{ 
    public void ImportantMethodThatMustBeThreadSafe() 
    { 
     // Do things 
    } 
} 

Der Einsatz von Generika macht es möglich, eine gefälschte Sperre in Ihren Tests zu verwenden, um zu überprüfen, dass die Sperre immer genommen wird, wenn eine Transaktion erstellt wird und erst freigegeben, wenn Transaktion angeordnet ist, . Jetzt können Sie auch überprüfen, ob die Sperre beim Aufruf Ihrer wichtigen Methode übernommen wurde. Die Verwendung in der Produktion Code sollte wie folgt aussehen:

// Make sure this is the only way to create ImportantImplementation. 
// Consider making ImportantImplementation an internal class of the provider. 
ImportantProvider<LockWithMutex> provider = 
    new ImportantProvider<LockWithMutex>(new ImportantImplementation()); 

// Create a transaction that will be disposed when no longer used. 
using (ImportantTransaction transaction = provider.CreateTransaction()) 
{ 
    // Access your object thread safe. 
    transaction.Facade.ImportantMethodThatMustBeThreadSafe(); 
} 

Indem sichergestellt wird die ImportantImplementation kann nicht von jemand anderem erstellt werden (zum Beispiel durch es in dem Provider erstellen und eine private Klasse machen) Sie kan jetzt beweisen Klasse ist threadsicher, da auf sie ohne eine Transaktion nicht zugegriffen werden kann und die Transaktion die Sperre immer bei der Erstellung übernimmt und bei der Entsorgung wieder freigibt.

Stellen Sie sicher, dass die Transaktion richtig angeordnet ist, kann schwieriger sein und wenn nicht, können Sie seltsames Verhalten in Ihrer Anwendung sehen. Sie können Tools wie Microsoft Chess verwenden (wie in einem anderen Anser vorgeschlagen), um nach solchen Dingen zu suchen.Oder Sie können Ihren Provider implementieren, um die Fassade haben und sie es so machen implementieren:

public void ImportantMethodThatMustBeThreadSafe() 
    { 
     using (ImportantTransaction transaction = CreateTransaction()) 
     { 
      transaction.Facade.ImportantMethodThatMustBeThreadSafe(); 
     } 
    } 

Auch wenn dies ist die Implementierung ich Sie hoffen kann, die Tests herauszufinden, diese Klassen zu überprüfen, je nach Bedarf.

5

Beachten Sie, dass Drors Antwort dies nicht explizit sagt, aber zumindest Chess (und wahrscheinlich Racer) arbeiten, indem sie eine Reihe von Threads durch alle möglichen Interleavings laufen lassen, um reproduzierbare Fehler zu erhalten. Sie lassen die Threads nicht nur eine Weile laufen, in der Hoffnung, dass es bei einem Fehler zufällig passieren wird.

Chess zum Beispiel wird alle Interleavings durchlaufen und Ihnen dann eine Tag-Zeichenfolge geben, die das Interleaving darstellt, auf dem ein Deadlock gefunden wurde, sodass Sie Ihre Tests mit den spezifischen Interleavings aus einer Deadlocking-Perspektive verknüpfen können.

Ich kenne nicht die genaue interne Funktionsweise dieses Tools und wie es diese Tag-Strings zurück zu Code, den Sie möglicherweise ändern, um einen Deadlock zu beheben, aber da haben Sie es ... Ich bin wirklich sehr gespannt Dieses Tool (und Pex) wird Teil der VS IDE.

1

Obwohl es ein Werkzeug wie mit wie Racer oder Schach nicht so elegant ist, habe ich diese Art der Sache für die Prüfung für Thread-Sicherheit verwendet:

// from linqpad 

void Main() 
{ 
    var duration = TimeSpan.FromSeconds(5); 
    var td = new ThreadDangerous(); 

    // no problems using single thread (run this for as long as you want) 
    foreach (var x in Until(duration)) 
     td.DoSomething(); 

    // thread dangerous - it won't take long at all for this to blow up 
    try 
    {   
     Parallel.ForEach(WhileTrue(), x => 
      td.DoSomething()); 

     throw new Exception("A ThreadDangerException should have been thrown"); 
    } 
    catch(AggregateException aex) 
    { 
     // make sure that the exception thrown was related 
     // to thread danger 
     foreach (var ex in aex.Flatten().InnerExceptions) 
     { 
      if (!(ex is ThreadDangerException)) 
       throw; 
     } 
    } 

    // no problems using multiple threads (run this for as long as you want) 
    var ts = new ThreadSafe(); 
    Parallel.ForEach(Until(duration), x => 
     ts.DoSomething());  

} 

class ThreadDangerous 
{ 
    private Guid test; 
    private readonly Guid ctrl; 

    public void DoSomething() 
    {   
     test = Guid.NewGuid(); 
     test = ctrl;   

     if (test != ctrl) 
      throw new ThreadDangerException(); 
    } 
} 

class ThreadSafe 
{ 
    private Guid test; 
    private readonly Guid ctrl; 
    private readonly object _lock = new Object(); 

    public void DoSomething() 
    { 
     lock(_lock) 
     { 
      test = Guid.NewGuid(); 
      test = ctrl;   

      if (test != ctrl) 
       throw new ThreadDangerException(); 
     } 
    } 
} 

class ThreadDangerException : Exception 
{ 
    public ThreadDangerException() : base("Not thread safe") { } 
} 

IEnumerable<ulong> Until(TimeSpan duration) 
{ 
    var until = DateTime.Now.Add(duration); 
    ulong i = 0; 
    while (DateTime.Now < until) 
    { 
     yield return i++; 
    } 
} 

IEnumerable<ulong> WhileTrue() 
{ 
    ulong i = 0; 
    while (true) 
    { 
     yield return i++; 
    } 
} 

Die Theorie ist, dass, wenn Sie einen Thread gefährlichen Zustand führen können Konsequent in sehr kurzer Zeit auftreten, sollten Sie in der Lage sein, Thread-sichere Bedingungen herbeizuführen und sie zu verifizieren, indem Sie relativ lange warten, ohne die Zustandsbeschädigung zu beobachten.

Ich gebe zu, dass dies eine primitive Art und Weise zu gehen ist und möglicherweise nicht in komplexen Szenarien helfen.

0

Hier ist mein Ansatz. Dieser Test befasst sich nicht mit Deadlocks, sondern mit der Konsistenz. Ich teste eine Methode mit einem synchronisierten Block, mit Code, der etwa wie folgt aussieht:

synchronized(this) { 
    int size = myList.size(); 
    // do something that needs "size" to be correct, 
    // but which will change the size at the end. 
    ... 
} 

Es ist schwierig, ein Szenario zu erzeugen, die zuverlässig einen Thread Konflikt produzieren, aber hier ist, was ich tat.

Zuerst erstellt mein Komponententest 50 Threads, startete sie alle zur gleichen Zeit und ließ sie alle meine Methode aufrufen. Ich benutze einen CountDown Latch sie alle zur gleichen Zeit zu starten:

CountDownLatch latch = new CountDownLatch(1); 
for (int i=0; i<50; ++i) { 
    Runnable runner = new Runnable() { 
    latch.await(); // actually, surround this with try/catch InterruptedException 
    testMethod(); 
    } 
    new Thread(runner, "Test Thread " +ii).start(); // I always name my threads. 
} 
// all threads are now waiting on the latch. 
latch.countDown(); // release the latch 
// all threads are now running the test method at the same time. 

Dies kann oder kann nicht einen Konflikt erzeugen. Meine testMethod() sollte eine Ausnahme auslösen können, wenn ein Konflikt auftritt. Aber wir können noch nicht sicher sein, dass dies zu einem Konflikt führen wird. Wir wissen also nicht, ob der Test gültig ist. Also hier ist der Trick: Kommentieren Sie Ihre synchronisierten Schlüsselwort (e) und führen Sie den Test aus. Wenn dies zu einem Konflikt führt, wird der Test fehlschlagen. Wenn es ohne das synchronisierte Schlüsselwort fehlschlägt, ist Ihr Test gültig.

Das habe ich gemacht, und mein Test ist nicht ausgefallen, also war es (noch) kein gültiger Test. Aber ich war in der Lage, einen Fehler zuverlässig zu produzieren, indem ich den obigen Code in eine Schleife setzte und ihn 100 Mal hintereinander ablaufen ließ. Also rufe ich die Methode 5000 mal an. (Ja, das erzeugt einen langsamen Test. Mach dir keine Sorgen. Deine Kunden werden davon nicht gestört, also solltest du auch nicht.)

Sobald ich diesen Code in eine äußere Schleife, I war in der Lage, einen Fehler bei der 20. Iteration der äußeren Schleife zuverlässig zu erkennen.Jetzt war ich zuversichtlich, dass der Test gültig war, und ich stellte die synchronisierten Schlüsselwörter wieder her, um den eigentlichen Test auszuführen. (Es hat funktioniert.)

Möglicherweise stellen Sie fest, dass der Test auf einer Maschine und nicht auf einer anderen Maschine gültig ist. Wenn der Test auf einem Computer gültig ist und Ihre Methoden den Test bestehen, ist er vermutlich Thread-sicher auf allen Computern. Sie sollten jedoch auf der Maschine, auf der Ihre nächtlichen Komponententests ausgeführt werden, auf Gültigkeit prüfen.

Verwandte Themen