문제

다중 스레드 애플리케이션을 작성할 때 경험하는 가장 일반적인 문제 중 하나는 경쟁 조건입니다.

커뮤니티에 대한 내 질문은 다음과 같습니다.

경쟁 조건이란 무엇입니까?어떻게 감지합니까?어떻게 처리합니까?마지막으로 이러한 일이 발생하지 않도록 하려면 어떻게 해야 합니까?

도움이 되었습니까?

해결책

경쟁 조건은 두 개 이상의 스레드가 공유 데이터에 액세스할 수 있고 동시에 변경하려고 할 때 발생합니다.스레드 예약 알고리즘은 언제든지 스레드 간에 교환할 수 있으므로 스레드가 공유 데이터에 액세스하려고 시도하는 순서를 알 수 없습니다.따라서 데이터 변경 결과는 스레드 스케줄링 알고리즘에 따라 달라집니다.두 스레드 모두 데이터에 액세스/변경하기 위해 "경주"하고 있습니다.

한 스레드가 "확인 후 조치"를 수행할 때 문제가 자주 발생합니다(예:값이 X이면 "검사"한 다음 X인 값에 따라 작업을 수행하기 위해 "동작"하고 다른 스레드는 "검사"와 "동작" 사이의 값에 대해 작업을 수행합니다.예:

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.
}

요점은 다른 스레드가 확인과 실행 사이에 x를 변경했는지 여부에 따라 y가 10일 수도 있고 무엇이든 될 수 있다는 것입니다.당신은 실제로 알 방법이 없습니다.

경쟁 조건이 발생하는 것을 방지하기 위해 일반적으로 공유 데이터 주위에 잠금을 설정하여 한 번에 하나의 스레드만 데이터에 액세스할 수 있도록 합니다.이는 다음과 같은 의미입니다.

// 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

다른 팁

공유 리소스에 액세스하는 다중 스레드(또는 병렬) 코드가 예상치 못한 결과를 초래할 수 있는 경우 "경합 조건"이 존재합니다.

다음 예를 들어보세요.

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

이 코드를 동시에 실행하는 스레드가 5개라면 x 값은 50,000,000이 되지 않습니다.실제로 각 실행마다 다를 수 있습니다.

그 이유는 각 스레드가 x 값을 증가시키려면 다음을 수행해야 하기 때문입니다.(단순화, 분명히)

Retrieve the value of x
Add 1 to this value
Store this value to x

모든 스레드는 언제든지 이 프로세스의 어느 단계에나 있을 수 있으며, 공유 리소스가 관련되면 서로를 밟을 수 있습니다.x를 읽는 동안과 다시 쓰는 동안 다른 스레드에 의해 x의 상태가 변경될 수 있습니다.

스레드가 x 값을 검색했지만 아직 저장하지 않았다고 가정해 보겠습니다.다른 스레드도 같은 (아직 스레드가 변경하지 않았기 때문에) x 값을 저장하고 둘 다 같은 값 (x+1)을 다시 x로!

예:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, 값은 7입니다
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: x에 8개 저장

일종의 경쟁 조건을 사용하면 피할 수 있습니다. 잠금 공유 리소스에 액세스하는 코드 이전의 메커니즘:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

여기서는 매번 50,000,000이라는 답이 나옵니다.

잠금에 대한 자세한 내용을 보려면 다음을 검색하세요.뮤텍스, 세마포어, 중요 섹션, 공유 리소스.

경쟁 조건이란 무엇입니까?

당신은 오후 5시에 영화를 보러 갈 계획입니다.오후 4시에 티켓 구매 가능 여부를 문의합니다.담당자는 가능하다고 합니다.긴장을 풀고 공연 5분 전에 매표소에 도착합니다.나는 당신이 무슨 일이 일어날지 추측할 수 있다고 확신합니다:풀하우스예요.여기서 문제는 확인과 작업 사이의 기간에 있었습니다.4시에 문의하시고 5시에 행동하셨습니다.그 사이에 다른 사람이 티켓을 가져갔습니다.이는 경쟁 조건입니다. 특히 경쟁 조건에 대한 "확인 후 조치" 시나리오입니다.

어떻게 감지합니까?

종교 코드 검토, 다중 스레드 단위 테스트.지름길은 없습니다.여기에 등장하는 Eclipse 플러그인은 거의 없지만 아직 안정적인 것은 없습니다.

어떻게 처리하고 예방하나요?

가장 좋은 방법은 부작용이 없고 상태가 없는 함수를 만들고, 불변성을 최대한 사용하는 것입니다.그러나 그것이 항상 가능한 것은 아닙니다.따라서 java.util.concurrent.atomic, 동시 데이터 구조, 적절한 동기화 및 행위자 기반 동시성을 사용하면 도움이 됩니다.

동시성을 위한 최고의 리소스는 JCIP입니다.당신은 또한 더 많은 것을 얻을 수 있습니다 위의 설명에 대한 자세한 내용은 여기를 참조하세요..

경쟁 조건과 데이터 경쟁 사이에는 중요한 기술적 차이가 있습니다.대부분의 답변은 이러한 용어가 동일하다고 가정하는 것 같지만 그렇지 않습니다.

2개의 명령어가 동일한 메모리 위치에 액세스할 때 데이터 경합이 발생합니다. 이러한 액세스 중 적어도 하나는 쓰기이고 액세스가 없습니다. 주문하기 전에 발생 액세스할 수 있습니다.이제 주문 전 발생을 구성하는 것은 많은 논쟁의 대상이 되지만 일반적으로 동일한 잠금 변수의 ulock-lock 쌍과 동일한 조건 변수의 대기 신호 쌍은 사전 발생 순서를 유도합니다.

경쟁 조건은 의미론적 오류입니다.잘못된 프로그램을 발생시키는 이벤트의 타이밍이나 순서에서 발생하는 결함입니다. 행동.

많은 경쟁 조건이 데이터 경합으로 인해 발생할 수 있지만 실제로는 그럴 필요가 없습니다.사실, 데이터 경합과 경합 조건은 서로에게 필요조건도 아니고 충분조건도 아닙니다. 이것 블로그 게시물에서도 간단한 은행 거래 예시를 통해 차이점을 잘 설명하고 있습니다.여기 또 다른 간단한 것이 있습니다 차이점을 설명하는 것입니다.

이제 용어를 정의했으므로 원래 질문에 답해 보겠습니다.

경쟁 조건은 의미론적 버그이므로 이를 탐지할 수 있는 일반적인 방법은 없습니다.이는 올바른 것과 올바른 것을 구별할 수 있는 자동화된 오라클을 가질 수 있는 방법이 없기 때문입니다.일반적인 경우 잘못된 프로그램 동작.인종 감지는 결정 불가능한 문제입니다.

반면, 데이터 경합은 반드시 정확성과 관련이 없는 정확한 정의를 갖고 있으므로 이를 감지할 수 있습니다.데이터 경합 감지기에는 다양한 유형이 있습니다(정적/동적 데이터 경합 감지, 잠금 세트 기반 데이터 경합 감지, 발생 전 기반 데이터 경합 감지, 하이브리드 데이터 경합 감지).최첨단 동적 데이터 경합 감지기는 다음과 같습니다. ThreadSanitizer 실제로는 매우 잘 작동합니다.

일반적으로 데이터 경합을 처리하려면 공유 데이터에 대한 액세스 간의 에지 발생 전(개발 중 또는 위에서 언급한 도구를 사용하여 감지된 후)을 유도하기 위한 일부 프로그래밍 규율이 필요합니다.이는 잠금, 조건 변수, 세마포어 등을 통해 수행될 수 있습니다.그러나 구성에 따른 데이터 경합을 방지하는 공유 메모리 대신 메시지 전달과 같은 다양한 프로그래밍 패러다임을 사용할 수도 있습니다.

일종의 표준적인 정의는 "두 스레드가 동시에 메모리의 동일한 위치에 액세스하고 액세스 중 적어도 하나가 쓰기인 경우." 상황에서 "리더" 스레드는 어떤 스레드가 "경합에서 승리"하는지에 따라 이전 값 또는 새 값을 얻을 수 있습니다. 이것이 항상 버그는 아닙니다. 실제로 일부 매우 털이 많은 저수준 알고리즘은 이 작업을 수행합니다. 그러나 일반적으로 피해야 합니다.@Steve Gury는 언제 문제가 될 수 있는지에 대한 좋은 예를 제공합니다.

경쟁 조건은 특정 시간적 조건에서만 발생하는 일종의 버그입니다.

예:A와 B라는 두 개의 스레드가 있다고 상상해보십시오.

스레드 A에서:

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

스레드 B에서:

object.a = 0

object.a가 null이 아닌지 확인한 직후 스레드 A가 선점되면 B는 다음을 수행합니다. a = 0, 스레드 A가 프로세서를 확보하면 "0으로 나누기"를 수행합니다.

이 버그는 if 문 바로 다음에 스레드 A가 선점될 때만 발생하며 매우 드물지만 발생할 수 있습니다.

경쟁 조건은 다중 스레드 응용 프로그램이나 다중 프로세스 시스템에서 발생합니다.가장 기본적인 경쟁 조건은 동일한 스레드나 프로세스에 있지 않은 두 가지 일이 이를 보장하기 위한 조치를 취하지 않고 특정 순서로 발생한다고 가정하는 모든 것입니다.이는 일반적으로 두 스레드가 모두 액세스할 수 있는 클래스의 멤버 변수를 설정하고 확인하여 메시지를 전달할 때 발생합니다.한 스레드가 다른 스레드에 작업을 완료할 시간을 제공하기 위해 절전 모드를 호출할 때 거의 항상 경쟁 조건이 발생합니다(일부 확인 메커니즘을 사용하여 절전 모드가 루프에 있지 않는 한).

경합 상태를 방지하는 도구는 언어와 OS에 따라 다르지만 일반적인 도구로는 뮤텍스, 임계 섹션 및 신호가 있습니다.뮤텍스는 당신이 뭔가를 하고 있는 유일한 사람인지 확인하고 싶을 때 좋습니다.다른 사람이 작업을 완료했는지 확인하고 싶을 때 신호가 좋습니다.공유 리소스를 최소화하면 예기치 않은 동작을 방지하는 데도 도움이 될 수 있습니다.

경쟁 조건을 감지하는 것은 어려울 수 있지만 몇 가지 징후가 있습니다.절전 모드에 크게 의존하는 코드는 경쟁 조건이 발생하기 쉽기 때문에 먼저 영향을 받는 코드에서 절전 모드 호출을 확인하세요.특히 긴 절전 모드를 추가하면 디버깅을 통해 특정 이벤트 순서를 강제로 적용할 수도 있습니다.이는 동작을 재현하고, 타이밍을 변경하여 동작을 사라지게 할 수 있는지 확인하고, 솔루션을 테스트하는 데 유용할 수 있습니다.디버깅 후에는 절전 모드를 제거해야 합니다.

그러나 경쟁 조건이 있다는 특징은 일부 시스템에서만 간헐적으로 발생하는 문제가 있는 경우입니다.일반적인 버그는 충돌과 교착 상태입니다.로깅을 사용하면 영향을 받은 영역을 찾아 다시 작업할 수 있습니다.

경쟁 조건은 소프트웨어뿐만 아니라 하드웨어와도 관련이 있습니다.실제로 이 용어는 처음에는 하드웨어 산업에서 만들어졌습니다.

에 따르면 위키피디아:

이 용어는 의 아이디어에서 유래되었습니다. 서로 경주하는 두 신호 에게 먼저 출력에 영향을 미침.

논리 회로의 경쟁 조건:

enter image description here

소프트웨어 업계에서는 이 용어를 그대로 사용했기 때문에 이해하기가 조금 어렵습니다.

이를 소프트웨어 세계에 매핑하려면 몇 가지 교체 작업을 수행해야 합니다.

  • "두 개의 신호" => "두 개의 스레드"/"두 개의 프로세스"
  • "influence the output" => "일부 공유 상태에 영향을 미침"

따라서 소프트웨어 산업의 경쟁 조건은 "일부 공유 상태에 영향을 미치기 위해" 서로 경쟁하는 "두 개의 스레드"/"두 개의 프로세스"를 의미하며, 공유 상태의 최종 결과는 특정 특정 원인으로 인해 발생할 수 있는 미묘한 타이밍 차이에 따라 달라집니다. 스레드/프로세스 실행 순서, 스레드/프로세스 스케줄링 등

경쟁 조건은 두 개의 동시 스레드 또는 프로세스가 리소스를 놓고 경쟁하고 최종 상태는 누가 리소스를 먼저 가져오는지에 따라 달라지는 동시 프로그래밍의 상황입니다.

Microsoft는 실제로 매우 상세한 내용을 발표했습니다. 기사 경쟁 조건과 교착 상태에 관한 문제입니다.가장 요약된 초록은 제목 단락입니다.

두 스레드가 공유 변수에 동시에 액세스 할 때 레이스 조건이 발생합니다.첫 번째 스레드는 변수를 읽고 두 번째 스레드는 변수와 동일한 값을 읽습니다.그런 다음 첫 번째 스레드와 두 번째 스레드는 값에 대한 작업을 수행하며 공유 변수에 마지막 값을 기록 할 수있는 스레드를 확인하기 위해 경주합니다.마지막으로 값을 기록하는 스레드의 값은 이전 스레드가 쓴 값에 대해 쓰기를 쓰기 때문에 보존됩니다.

경쟁 조건이란 무엇입니까?

프로세스가 다른 이벤트의 순서나 타이밍에 크게 의존하는 상황입니다.

예를 들어 프로세서 A 및 프로세서 B 둘 다 필요 실행을 위한 동일한 리소스.

어떻게 감지합니까?

경합 상태를 자동으로 감지하는 도구가 있습니다.

어떻게 처리합니까?

경쟁 조건은 다음으로 처리할 수 있습니다. 뮤텍스 또는 세마포어.경쟁 조건을 방지하기 위해 프로세스가 특정 요구 사항에 따라 리소스를 획득할 수 있도록 하는 잠금 역할을 합니다.

이러한 일이 발생하지 않도록 어떻게 방지합니까?

경쟁 상태를 방지하는 방법에는 다음과 같은 다양한 방법이 있습니다. 중요 섹션 회피.

  1. 중요 영역 내에서는 동시에 두 개의 프로세스가 없습니다.(상호 배제)
  2. 속도나 CPU 수에 대해서는 어떠한 가정도 하지 않습니다.
  3. 다른 프로세스를 차단하는 중요 영역 외부에서 실행되는 프로세스가 없습니다.
  4. 어떤 프로세스도 임계 영역에 들어가기 위해 영원히 기다릴 필요가 없습니다.(A는 B 자원을 기다리고, B는 C 자원을 기다리고, C는 A 자원을 기다립니다.)

경쟁 조건은 장치나 시스템이 동시에 두 개 이상의 작업을 수행하려고 시도하지만 장치나 시스템의 특성상 작업이 올바른 순서로 수행되어야 하는 바람직하지 않은 상황입니다. 올바르게 수행되었습니다.

컴퓨터 메모리나 스토리지에서는 많은 양의 데이터를 읽고 쓰라는 명령이 거의 동시에 수신되고, 해당 오래된 데이터가 남아 있는 동안 기계가 오래된 데이터의 일부 또는 전체를 덮어쓰려고 시도하면 경쟁 조건이 발생할 수 있습니다. 읽다.결과는 다음 중 하나 이상이 될 수 있습니다.컴퓨터 충돌, "불법 조작", 프로그램 알림 및 종료, 이전 데이터 읽기 오류 또는 새 데이터 쓰기 오류.

다음은 초보자가 Java의 스레드를 쉽게 이해하는 데 도움이 되는 고전적인 은행 계좌 잔고 예입니다.경쟁 조건:

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));

}

경쟁 조건을 더 잘 이해하려면 다음 기본 예를 시도해 보세요.

    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;
        }
    }
}

항상 경쟁 조건을 폐기하고 싶지는 않습니다.여러 스레드에서 읽고 쓸 수 있는 플래그가 있고 이 플래그가 한 스레드에 의해 'done'으로 설정되어 플래그가 'done'으로 설정되면 다른 스레드가 처리를 중지하는 경우 "경주"를 원하지 않습니다. 조건"을 제거합니다.실제로 이것은 양성 경쟁 조건이라고 할 수 있습니다.

그러나 경쟁 조건 탐지 도구를 사용하면 유해한 경쟁 조건으로 발견됩니다.

경쟁 조건에 대한 자세한 내용은 여기를 참조하세요. http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.

카운트가 증가하자마자 카운트를 표시해야 하는 작업을 생각해 보세요.즉, 곧 카운터스레드 값을 증가시킵니다 디스플레이스레드 최근 업데이트된 값을 표시해야 합니다.

int i = 0;

산출

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

여기 카운터스레드 자주 잠금을 받고 이전에 값을 업데이트합니다. 디스플레이스레드 표시합니다.여기에 경쟁 조건이 있습니다.경쟁 조건은 동기화를 사용하여 해결할 수 있습니다.

당신은 할 수 있습니다 경쟁 조건 방지, "Atomic" 클래스를 사용하는 경우.그 이유는 스레드가 작업 가져오기 및 설정을 분리하지 않기 때문입니다. 예는 아래와 같습니다.

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

결과적으로 링크 "ai"에는 7개가 있게 됩니다.두 가지 작업을 수행했지만 두 작업 모두 동일한 스레드를 확인하고 다른 스레드가 이를 방해하지 않으므로 경쟁 조건이 없음을 의미합니다!

경쟁 조건은 두 개 이상의 프로세스가 동시에 공유 데이터에 액세스하고 이를 변경할 때 발생하는 바람직하지 않은 상황입니다. 이는 리소스에 대한 액세스 충돌이 있기 때문에 발생합니다.중요 섹션 문제로 인해 경쟁 조건이 발생할 수 있습니다.프로세스 중 임계 조건을 해결하기 위해 임계 섹션을 실행하는 한 번에 하나의 프로세스만 꺼냈습니다.

public class Synchronized_RACECONDITION {
    private static final int NUM_INCREMENTS = 10000;

    private static int count = 0;

    public static void main(String[] args) {
        testSyncIncrement();
        testNonSyncIncrement();
    }

    private static void testSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::incrementSync));

        ConcurrentUtils.stop(executor);

        System.out.println("   Sync: " + count);
    }

    private static void testNonSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::increment));

        ConcurrentUtils.stop(executor);

        System.out.println("NonSync: " + count);
    }

    private static synchronized void incrementSync() {
        count = count + 1;
    }

    private static void increment() {
        count = count + 1;
    }
static  class ConcurrentUtils {

    public static void stop(ExecutorService executor) {
        try {
            executor.shutdown();
            executor.awaitTermination(60, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
            System.err.println("termination interrupted");
        }
        finally {
            if (!executor.isTerminated()) {
                System.err.println("killing non-finished tasks");
            }
            executor.shutdownNow();
        }
    }
}
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top