글 작성자: Key Ryung

Introduction

동시성 문제를 말하면 항상 나오는 레이스 컨디션무엇인지는 설명할 수 있어도 생기는지 본질적인 이유를 알지 못하는 것 같아 왜 생기는지를 컴퓨터 구조부터 언어 측면까지 정리해보려고 한다. 이번 글에서는 특히 상태를 갖는다는 것이 Race Condition에 어떤 영향을 주는지까지 정리하려 한다.

 

Concurrency bug(동시성 문제)

동시성 문제란 우리가 동시성을 사용함으로써 발생하는 문제들을 말한다.

그럼 여기서 동시성이란 프로그램이나 알고리즘의 단위가 순서에 관계없이 혹은 부분적으로 실행되는 능력을 말한다. 자세히 설명하면 concurrent unit이라고 말하는 단위의 병렬 실행을 허용하여 multi-processor, multi-core 시스템의 성능을 향상시켜주기 위한 목적이라고도 설명할 수 있다.

 

근데 동시성에서 어떤 문제가 생긴다는 것일까?

 

동시성을 설명할 때 한 조건을 말하지 않았다.

동시성을 만족하려면 이런 동작들이 "최종 결과"에 영향을 주지 않아야 한다.

 

추가적으로 우리는 "한정된 자원"을 사용할 수 밖에 없다. 따라서 유한한 자원에서 동시성을 달성하고 시스템의 성능을 향상시키려고 하였으나 문제가 생겨 동시성도 만족하지 못하고 문제가 발생하는 것을 Concurrency Problem, Bug, Issue 라고 말한다.

 

그래서 해결하는 방법이 뭐에요?

나도 처음에는 동시성 문제가 정말 쉽게 풀리는 문제라고 오해하고 있었다. 동시성을 없애면 해결할 수 있으니까... 동시성을 사용하지 않더라도 충분히 컴퓨터는 빠르니까!

하지만 이런 해결책은 마치 인간이 불이 위험하다고 생각하여 불을 사용하지 않는 것이라 생각이 든다.

 

그래서 다시 처음으로 돌아가 동시성 문제가 발생하는 원인이 무엇인지 하나씩 정리해보려고 한다.

 

Side Effect

번역하면 "작용(수 효과)"이라고 사용하는데 정적인 의미로 많이 쓰인다.(두 개의 한자가 다르다!) 하지만 실제 뜻은 부차적인 작용, 부수적인 작용으로 무언가를 하려고 했는데 부수적으로 생기는 작용일 뿐 긍정적, 부정적이지도 않다.

프로그래밍에서 효과란 "외부 세계와의 모든 상호 작용", 따라서 Side Effect란 "값을 반환하는 메서드나 함수가 상태를 바꾸는 것"이다. 근데 왜 부수 효과가 동시성 문제를 일으킬까?

 

Race Condition

예시를 통해 경합 "조건"에서의 조건이 어떤 상황을 나타내는 것인지 이해하는 것이 좋을 것 같다.

나랑 여자 친구랑 커플 통장을 쓰고 있는데 잘 모아서 100만 원을 모아놨다. 어느 날 여자친구가 커플 계좌에서 75만원을 인출해 구찌 신발을 사려고 하고, 나는 이번에 출시되는 3050 그래픽 카드가 탑재된 조립 컴퓨터를 100만원을 인출해 사려고 동시에 시도했을 때, 무슨 일이 일어날까?

 

여기서 동시에 결제하려고 하는 이 조건을 Race condition(경합 조건)이라고 한다. 좀 더 구체적으로 프로그래밍 세계에서 정의하자면

2개 이상의 프로그램, 스레드가 같은 자원에 동시에 접근하고,
자원 사용 순서에 따라 결과가 달라지는 경우

 

(프로그램(프로세스), 스레드와 관련된 내용은 다음 시리즈에서 더 다룬다. 지금은 Race Condition에 대해서 먼저 이해하자)

위에서의 상황을 코드로 옮겨보겠다. (Side Effect와 Race Condition에 초점을 맞춘 코드, 올바른 OOP 구조는 아닌 것 같다.)

package com.keydo.entity;

public class Account {
    private Long balance;
    public Account(Long balance) {
        this.balance = balance;
    }

    public Long getBalance() {
        return this.balance;
    }

    public void withDrawn(Long payAmount) {
        assert(this.balance - payAmount > 0)
        this.balance -= payAmount;
    }
}

public class User {
    private String name;
    private Account account;

    public User(String name, Account account) {
        this.name = name;
        this.account = account;
    }

    public void payForBuy(Long payAmount) {
        account.withDrawn(payAmount);
    }
}

public class BankApplication {

    public static void main(String args[]) {
        Account account = new Account(100);        
        User key = new User("key", account);
        User lani = new User("lani", account);

        key.payForBuy(100); lani.payForBuy(75); // 동시에 실행된다고 가정하면, 경합조건 발생
    }
}

Thread를 사용했다면 실제로도 경합 조건이 발생하는 코드였겠지만 Race Condition과 Side Effect에 대해 초점을 맞췄다.

  • 두 User가 같은 account를 공유.
  • Account는 잔고라는 상태(필드)를 갖고 있음.
  • Account에서 withDrawn 메서드를 호출하여 남아있는 잔고에서 인출해야 하는 값을 빼줌
  • User는 payForBuy 메소드를 호출할 때 Account에서 지불해야 되는 금액을 입력함
  • 두 User가 메서드를 동시에 호출했다고 가정!

 

앞서 말했던 레이스 컨디션의 정의에 따라 분석해보면 Account를 누가 먼저 사용하느냐에 따라 결과가 달라진다. 내가 먼저 결제를 한다면 이후 여자 친구의 결제는 실패할 것이고, 여자 친구가 먼저 결제를 한다면 내가 사고 싶은 컴퓨터를 못 살 것이다.(잔고가 충분하다면 다 살 수 있겠지만...😭)

  • 같은 자원 : Account, balance
  • 다른 자원 사용 순서 : 누가 먼저 결제를 했는가? -> 경합 조건 성립!
  • 그럼 과연 컴퓨터는 누구의 편을 들어줘야 될까? 나 vs 여자 친구?
  • 컴퓨터는 누구의 편을 들어줄지 판단 못 함 - 결과를 못 내거나 잘못된 결과를 낼 수 있음

 

Side Effect - 그래서 이게 무슨 상관인데?!

Race Condition의 시작은 같은 자원 Account을 공유하는 것이다. 이때 Account 타입의 object는 balance라는 필드(상태)를 갖고 있다. 그리고 여기서 사용한 withDrawn, payForBuy 메서드는 balance를 바꾼다. (다시 한번 반복, 프로그래밍에서 Side Effect란 "값을 반환하는 메서드나 함수가 외부 상태를 바꾸는 것") 위 두 메소드는 SideEffect를 발생시킨다.

 

근본적인 문제의 발생은 같은 자원의 공유와 Side Effect이다.

 

너무나 당연하게 작성했던 상태를 변화시키는 메서드로 인해 Side Effect가 발생하고 이것이 결국 Race condition 문제로 이어진다. 더 근본적으로 들어가면 이 모든 것들은 상태를 가지고 있다는 것에서부터 출발한다.

 

이어서...

이번 편에서 상태를 갖는다는 것과 그로 인해 생기는 Side Effect, Race Condition까지를 정리했다. 다음 편에서는 그렇다면 상태(state)라는 것은 컴퓨터에게 어떤 의미를 가지길래 이런 문제들을 만들 수밖에 없는지 얘기해보고자 한다.


REF

[1] Joy Of Kotlin

[2] Reactive Programming

[3] https://www.youtube.com/watch?v=KGnXr62bgHM - Race Condition vs Data Races

[4] https://ko.wikipedia.org/wiki/%EB%B3%91%ED%96%89_%EC%BB%B4%ED%93%A8%ED%8C%85

[5] https://en.wikipedia.org/wiki/Concurrency_(computer_science)

 

Revision History

[2022.04.10]

내용 중 Safe Programming이라는 단어를 통해 왜 동시성 문제가 프로그램에 문제가 되는지 서술하고 싶었으나 재조사해보니 오래 전 자료라서 이 부분을 삭제

Stackoverflow에서도 이런 부분이 Safe programming이라는 주제에 관심이 있다는 것을 알리고 싶었으나 정작 찾은 글을 Secure programming이라서 삭제

[2022.04.01]

현재 만들고 있는 내용과 관련한 이름을 생각했을 때 Race Condition 보다 동시성 문제와 해결이라는 주제와 더 적합할 것 같아서 제목을 수정