multithreading - race - semaphore




Was ist eine Race-Bedingung? (12)

Beim Schreiben von Multithread-Anwendungen ist eines der am häufigsten auftretenden Probleme die Rennbedingungen.

Meine Fragen an die Community sind:

Was ist eine Race-Bedingung? Wie finden Sie sie? Wie gehst du mit ihnen um? Wie verhindern Sie schließlich, dass sie auftreten?


Was ist ein Rennzustand?

Sie planen, um 17 Uhr in einen Film zu gehen. Sie erkundigen sich um die Verfügbarkeit der Tickets um 16 Uhr. Der Vertreter sagt, dass sie verfügbar sind. Sie entspannen und erreichen das Ticketfenster 5 Minuten vor der Show. Ich bin sicher, Sie können erraten, was passiert: Es ist ein volles Haus. Das Problem lag hier in der Dauer zwischen dem Check und der Aktion. Du hast bei 4 nachgefragt und bei 5 gehandelt. In der Zwischenzeit hat jemand anderes die Tickets gegriffen. Das ist eine Race-Bedingung - speziell ein "check-then-act" -Szenario der Rennbedingungen.

Wie finden Sie sie?

Religiöse Überprüfung, Multithreading-Unit-Tests. Es gibt keine Abkürzung. Es gibt ein paar Eclipse-Plugins, die auf diesem erscheinen, aber noch nichts stabiles.

Wie gehst du damit um und verhinderst du?

Am besten wäre es, side-effect-freie und statusfreie Funktionen zu erstellen und möglichst viele immutable zu verwenden. Aber das ist nicht immer möglich. Die Verwendung von java.util.concurrent.atomic, gleichzeitige Datenstrukturen, korrekte Synchronisation und akteursbasierte Gleichzeitigkeit helfen dabei.

Die beste Ressource für Parallelität ist JCIP. Sie können hier auch mehr Details zur obigen Erklärung erfahren .


Du willst nicht immer eine Wettlaufbedingung verwerfen. Wenn Sie ein Flag haben, das von mehreren Threads gelesen und geschrieben werden kann, und dieses Flag von einem Thread auf 'done' gesetzt ist, so dass andere Threads die Verarbeitung stoppen, wenn Flag auf 'done' gesetzt ist, wollen Sie dieses 'race nicht Bedingung "beseitigt werden. In der Tat kann diese als eine gutartige Race-Bedingung bezeichnet werden.

Wird jedoch ein Werkzeug zur Erkennung des Race-Zustands verwendet, wird es als schädlicher Race-Zustand erkannt.

Weitere Details zur Rennkondition finden Sie hier, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .


Eine Art von kanonischer Definition ist " wenn zwei Threads gleichzeitig auf den gleichen Ort im Speicher zugreifen und mindestens einer der Zugriffe ein Schreibvorgang ist ." In der Situation kann der "Leser" -Thread den alten Wert oder den neuen Wert erhalten, abhängig davon, welcher Thread "das Rennen gewinnt". Das ist nicht immer ein Fehler - tatsächlich tun einige wirklich haarige Low-Level-Algorithmen dies absichtlich - aber es sollte im Allgemeinen vermieden werden. @ Steve Gury geben ein gutes Beispiel, wenn es ein Problem sein könnte.


Eine Race Condition ist eine Art Bug, der nur unter bestimmten zeitlichen Bedingungen auftritt.

Beispiel: Stellen Sie sich vor, Sie haben zwei Threads, A und B.

In Thread A:

if( object.a != 0 )
    object.avg = total / object.a

In Thema B:

object.a = 0

Wenn Thread A verhindert wird, nachdem überprüft wurde, dass object.a nicht null ist, wird B a = 0 tun, und wenn Thread A den Prozessor erhält, wird es eine "dividiere durch Null" tun.

Dieser Fehler tritt nur auf, wenn Thread A direkt nach der if-Anweisung vorweggenommen wird, es ist sehr selten, aber es kann passieren.


Eine Racebedingung tritt auf, wenn zwei oder mehr Threads auf freigegebene Daten zugreifen können und versuchen, sie gleichzeitig zu ändern. Da der Threadplanungsalgorithmus jederzeit zwischen Threads wechseln kann, ist die Reihenfolge nicht bekannt, in der die Threads versuchen, auf die freigegebenen Daten zuzugreifen. Das Ergebnis der Datenänderung hängt daher vom Thread-Scheduling-Algorithmus ab, dh beide Threads "rasen", um auf die Daten zuzugreifen / sie zu ändern.

Probleme treten oft auf, wenn ein Thread ein "check-then-act" durchführt (zB "check" wenn der Wert X ist, dann "act" um etwas zu tun, was davon abhängt, dass der Wert X ist) und ein anderer Thread etwas mit dem Wert in tut zwischen dem "check" und dem "act". Z.B:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

Der Punkt ist, y könnte 10 sein, oder es könnte alles sein, abhängig davon, ob ein anderer Thread x zwischen dem Check und dem Act geändert hat. Du hast keine wirkliche Art zu wissen.

Um das Auftreten von Race Conditions zu verhindern, würden Sie normalerweise die freigegebenen Daten sperren, um sicherzustellen, dass immer nur ein Thread auf die Daten zugreifen kann. Dies würde etwa so aussehen:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

Eine Wettlaufsituation ist eine unerwünschte Situation, die auftritt, wenn ein Gerät oder System versucht, zwei oder mehr Operationen gleichzeitig durchzuführen, aber aufgrund der Art der Vorrichtung oder des Systems müssen die Operationen in der richtigen Reihenfolge ausgeführt werden, um zu sein richtig gemacht.

In einem Computerspeicher oder -speicher kann eine Race-Bedingung auftreten, wenn Befehle zum Lesen und Schreiben einer großen Datenmenge nahezu zu demselben Zeitpunkt empfangen werden und die Maschine versucht, einige oder alle alten Daten zu überschreiben, während sich diese alten Daten noch befinden lesen. Das Ergebnis kann eines oder mehrere der folgenden sein: ein Computerabsturz, eine "illegale Operation", Benachrichtigung und Herunterfahren des Programms, Fehler beim Lesen der alten Daten oder Fehler beim Schreiben der neuen Daten.


Hier ist das klassische Beispiel für das Bankkonto-Konto, das Neulingen helfen wird, Threads in Java einfach zu verstehen:

public class BankAccount {

/**
 * @param args
 */
int accountNumber;
double accountBalance;

public synchronized boolean Deposit(double amount){
    double newAccountBalance=0;
    if(amount<=0){
        return false;
    }
    else {
        newAccountBalance = accountBalance+amount;
        accountBalance=newAccountBalance;
        return true;
    }

}
public synchronized boolean Withdraw(double amount){
    double newAccountBalance=0;
    if(amount>accountBalance){
        return false;
    }
    else{
        newAccountBalance = accountBalance-amount;
        accountBalance=newAccountBalance;
        return true;
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    BankAccount b = new BankAccount();
    b.accountBalance=2000;
    System.out.println(b.Withdraw(3000));

}

Microsoft hat tatsächlich einen wirklich ausführlichen article über Rennbedingungen und Deadlocks veröffentlicht. Die am meisten zusammengefasste Zusammenfassung davon wäre der Titelabsatz:

Eine Race-Bedingung tritt auf, wenn zwei Threads gleichzeitig auf eine gemeinsam genutzte Variable zugreifen. Der erste Thread liest die Variable und der zweite Thread liest den gleichen Wert aus der Variablen. Dann führen der erste Thread und der zweite Thread ihre Operationen mit dem Wert aus, und sie rasen, um zu sehen, welcher Thread den letzten Wert für die gemeinsam genutzte Variable schreiben kann. Der Wert des Threads, der seinen Wert zuletzt schreibt, wird beibehalten, da der Thread den Wert überschreibt, den der vorherige Thread geschrieben hat.


Race Condition bezieht sich nicht nur auf Software, sondern auch auf Hardware. Eigentlich wurde der Begriff zunächst von der Hardware-Industrie geprägt.

Laut wikipedia :

Der Begriff geht von der Idee aus, dass sich zwei Signale gegenseitig beeinflussen, um den Ausgang zuerst zu beeinflussen .

Race-Bedingung in einer logischen Schaltung:

Die Softwarebranche nahm diesen Begriff ohne Modifikation, was es ein wenig schwierig zu verstehen macht.

Sie müssen etwas ersetzen, um es der Softwarewelt zuzuordnen:

  • "zwei Signale" => "zwei Threads" / "zwei Prozesse"
  • "Beeinflusse die Ausgabe" => "Beeinflussung eines gemeinsamen Zustands"

Race Condition in der Softwareindustrie bedeutet also "zwei Threads" / "zwei Prozesse", die sich gegenseitig "beeinflussen", und das Endergebnis des Shared State hängt von einem subtilen Timing-Unterschied ab, der durch bestimmte Faktoren verursacht werden kann Thread- / Prozess-Startreihenfolge, Thread- / Prozessplanung usw.


Race Conditions treten in Multi-Thread-Anwendungen oder Multi-Prozess-Systemen auf. Eine Race Condition ist im Grunde genommen alles, was die Annahme voraussetzt, dass zwei Dinge, die nicht in demselben Thread oder Prozess sind, in einer bestimmten Reihenfolge passieren, ohne dafür zu sorgen, dass dies geschieht. Dies geschieht häufig, wenn zwei Threads Nachrichten übergeben, indem sie Mitgliedsvariablen einer Klasse festlegen und überprüfen, auf die beide zugreifen können. Es gibt fast immer eine Race Condition, wenn ein Thread Sleep aufruft, um einem anderen Thread Zeit zu geben, eine Task zu beenden (es sei denn, dieser Sleep befindet sich in einer Schleife, mit einem gewissen Prüfmechanismus).

Tools zum Verhindern von Race Conditions sind abhängig von der Sprache und dem Betriebssystem, aber einige sind Mutexe, kritische Abschnitte und Signale. Mutexe sind gut, wenn Sie sichergehen wollen, dass Sie als Einziger etwas tun. Signale sind gut, wenn Sie sicherstellen möchten, dass jemand anderes etwas getan hat. Die Minimierung von freigegebenen Ressourcen kann auch dazu beitragen, unerwartetes Verhalten zu verhindern

Rennen zu erkennen kann schwierig sein, aber es gibt ein paar Anzeichen. Ein Code, der stark auf "Sleep" basiert, ist anfällig für Race Conditions. Überprüfen Sie daher zunächst, ob Anrufe im betroffenen Code in den Ruhezustand versetzt werden. Das Hinzufügen von besonders langen Betten kann auch zum Debuggen verwendet werden, um eine bestimmte Reihenfolge von Ereignissen zu erzwingen. Dies kann nützlich sein, um das Verhalten zu reproduzieren, zu sehen, ob Sie es verschwinden lassen können, indem Sie das Timing der Dinge ändern, und um getestete Lösungen zu testen. Die Betten sollten nach dem Debuggen entfernt werden.

Das Unterschriftszeichen, dass man eine Race-Bedingung hat, ist jedoch, wenn ein Problem auftritt, das nur zeitweise auf einigen Maschinen auftritt. Häufige Bugs wären Abstürze und Deadlocks. Mit der Protokollierung sollten Sie den betroffenen Bereich finden und von dort aus arbeiten können.


Stellen Sie sich eine Operation vor, die den Zähler anzeigen soll, sobald der Zähler inkrementiert wird. dh sobald CounterThread den Wert erhöht, muss DisplayThread den kürzlich aktualisierten Wert anzeigen.

int i = 0;

Ausgabe

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

Hier erhält CounterThread die Sperre häufig und aktualisiert den Wert, bevor DisplayThread ihn anzeigt. Hier besteht eine Race-Bedingung. Race Condition kann mit Hilfe von Synchronization gelöst werden


Versuchen Sie dieses grundlegende Beispiel für ein besseres Verständnis der Rassenkondition:

    public class ThreadRaceCondition {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Account myAccount = new Account(22222222);

        // Expected deposit: 250
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.DEPOSIT, 5.00);
            t.start();
        }

        // Expected withdrawal: 50
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.WITHDRAW, 1.00);
            t.start();

        }

        // Temporary sleep to ensure all threads are completed. Don't use in
        // realworld :-)
        Thread.sleep(1000);
        // Expected account balance is 200
        System.out.println("Final Account Balance: "
                + myAccount.getAccountBalance());

    }

}

class Transaction extends Thread {

    public static enum TransactionType {
        DEPOSIT(1), WITHDRAW(2);

        private int value;

        private TransactionType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    };

    private TransactionType transactionType;
    private Account account;
    private double amount;

    /*
     * If transactionType == 1, deposit else if transactionType == 2 withdraw
     */
    public Transaction(Account account, TransactionType transactionType,
            double amount) {
        this.transactionType = transactionType;
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        switch (this.transactionType) {
        case DEPOSIT:
            deposit();
            printBalance();
            break;
        case WITHDRAW:
            withdraw();
            printBalance();
            break;
        default:
            System.out.println("NOT A VALID TRANSACTION");
        }
        ;
    }

    public void deposit() {
        this.account.deposit(this.amount);
    }

    public void withdraw() {
        this.account.withdraw(amount);
    }

    public void printBalance() {
        System.out.println(Thread.currentThread().getName()
                + " : TransactionType: " + this.transactionType + ", Amount: "
                + this.amount);
        System.out.println("Account Balance: "
                + this.account.getAccountBalance());
    }
}

class Account {
    private int accountNumber;
    private double accountBalance;

    public int getAccountNumber() {
        return accountNumber;
    }

    public double getAccountBalance() {
        return accountBalance;
    }

    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean deposit(double amount) {
        if (amount < 0) {
            return false;
        } else {
            accountBalance = accountBalance + amount;
            return true;
        }
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean withdraw(double amount) {
        if (amount > accountBalance) {
            return false;
        } else {
            accountBalance = accountBalance - amount;
            return true;
        }
    }
}




race-condition