developer tip

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

copycodes 2020. 9. 28. 09:20
반응형

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


다중 스레드 응용 프로그램을 작성할 때 가장 일반적인 문제 중 하나는 경합 상태입니다.

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

경쟁 조건이란 무엇입니까? 그들을 어떻게 감지합니까? 어떻게 처리합니까? 마지막으로, 발생을 어떻게 방지합니까?


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

한 스레드가 "check-then-act"(예 : 값이 X 인 경우 "check", 다음 값이 X에 의존하는 작업을 수행하기 위해 "act")를 수행하고 다른 스레드가 값에 대해 뭔가를 수행 할 때 종종 문제가 발생합니다. "체크"와 "행위"사이. 예 :

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

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

경합 상태가 발생하지 않도록하려면 일반적으로 공유 데이터 주위에 잠금을 설정하여 한 번에 하나의 스레드 만 데이터에 액세스 할 수 있도록합니다. 이것은 다음과 같은 것을 의미합니다.

// 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의 값을 증가시키기 위해 다음을 수행해야하기 때문입니다.

x의 값을 검색합니다.
이 값에 1 더하기
이 값을 x에 저장

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

스레드가 x의 값을 검색했지만 아직 저장하지 않았다고 가정 해 보겠습니다. 다른 스레드는 또한 동일한 x 값을 검색 할 수 있습니다 (아직 스레드가 변경하지 않았기 때문에). 그러면 둘 다 동일한 값 (x + 1)을 x에 다시 저장할 것입니다 !

예:

스레드 1 : x를 읽고 값은 7입니다.
스레드 1 : x에 1 더하기, 값은 이제 8
스레드 2 : x를 읽고 값은 7입니다.
스레드 1 : x에 8 개 저장
스레드 2 : x에 1을 더하고 값은 이제 8입니다.
스레드 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 입니다.

Handling data races in general requires some programming discipline to induce happens-before edges between accesses to shared data (either during development, or once they are detected using the above mentioned tools). this can be done through locks, condition variables, semaphores, etc. However, one can also employ different programming paradigms like message passing (instead of shared memory) that avoid data races by construction.


일종의 표준 정의는 " 두 스레드가 동시에 메모리의 동일한 위치에 액세스하고 액세스 중 적어도 하나가 쓰기 인 경우 "입니다. 상황에서 "독자"스레드는 "경쟁에서이기는"스레드에 따라 이전 값 또는 새 값을 얻을 수 있습니다. 이것은 항상 버그가 아닙니다. 사실 일부 매우 털이 많은 저수준 알고리즘이 의도적으로 이것을 수행하지만 일반적으로 피해야합니다. @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으로 나누기"를 수행합니다.

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


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

wikipedia 에 따르면 :

이 용어 는 출력에 먼저 영향미치기 위해 서로 경주 하는 두 신호 의 아이디어에서 시작되었습니다 .

논리 회로의 경쟁 조건 :

여기에 이미지 설명 입력

소프트웨어 산업은이 용어를 수정하지 않고 사용했기 때문에 이해하기가 조금 어렵습니다.

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

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

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


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

경쟁 조건을 방지하는 도구는 언어와 OS에 따라 다르지만 일부 공통된 도구는 뮤텍스, 중요 섹션 및 신호입니다. 뮤텍스는 당신이 뭔가를하는 유일한 사람인지 확인하고 싶을 때 좋습니다. 다른 사람이 무언가를 완료했는지 확인하고 싶을 때 신호가 좋습니다. 공유 리소스를 최소화하면 예기치 않은 동작을 방지 할 수도 있습니다.

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

그러나 경쟁 조건이 있다는 서명 신호는 일부 컴퓨터에서 간헐적으로 만 발생하는 문제가있는 경우입니다. 일반적인 버그는 충돌 및 교착 상태입니다. 로깅을 사용하면 영향을받는 영역을 찾고 거기에서 다시 작업 할 수 있습니다.


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


Microsoft는 실제로 경쟁 조건과 교착 상태에 대한 자세한 기사게시했습니다 . 가장 요약 된 초록은 제목 단락입니다.

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


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

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

예를 들어 프로세서 A와 프로세서 B는 모두 실행을 위해 동일한 리소스가 필요 합니다.

그들을 어떻게 감지합니까?

경쟁 조건을 자동으로 감지하는 도구가 있습니다.

어떻게 처리합니까?

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

발생을 어떻게 방지합니까?

Critical Section avoidance 와 같이 경합 상태를 방지하는 다양한 방법이 있습니다 .

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

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

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

}

경쟁 조건은 장치 또는 시스템이 동시에 두 개 이상의 작업을 수행하려고 할 때 발생하는 바람직하지 않은 상황이지만 장치 또는 시스템의 특성으로 인해 작업이 적절한 순서로 수행되어야합니다. 올바르게 완료되었습니다.

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


"Atomic"클래스를 사용하면 경쟁 조건을 방지 할 수 있습니다 . 그 이유는 스레드가 get 및 set 작업을 분리하지 않기 때문입니다. 예제는 다음과 같습니다.

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

As a result, you will have 7 in link "ai". Although you did two actions, but the both operation confirm the same thread and no one other thread will interfere to this, that means no race conditions!


Try this basic example for better understanding of race condition:

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

You don't always want to discard a race condition. If you have a flag which can be read and written by multiple threads, and this flag is set to 'done' by one thread so that other thread stop processing when flag is set to 'done', you don't want that "race condition" to be eliminated. In fact, this one can be referred to as a benign race condition.

However, using a tool for detection of race condition, it will be spotted as a harmful race condition.

More details on race condition here, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.


Consider an operation which has to display the count as soon as the count gets incremented. ie., as soon as CounterThread increments the value DisplayThread needs to display the recently updated value.

int i = 0;

Output

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

Here CounterThread gets the lock frequently and updates the value before DisplayThread displays it. Here exists a Race condition. Race Condition can be solved by using Synchronzation


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

참고 URL : https://stackoverflow.com/questions/34510/what-is-a-race-condition

반응형