ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Multithreading] 리소스 공유와 임계 영역
    Java/Multi Threading 기초 2022. 10. 27. 12:58

    Java Multithreading

    멀티스레딩의 개념과 Java에서의 활용법을 공부하고 정리하는 시리즈입니다.


    이전 포스팅까지는 주로 멀티스레딩 자체에 대한 설명이나, 멀티스레딩을 통해 개선할 수 있는 부분 위주로 살펴본 것 같습니다.
    그러면 이제는 멀티스레딩을 부주의하게 사용했을 때에 어떤 문제를 직면할 수 있는지 살펴보겠습니다. 꼭 new Thread 혹은 extends Thread라고 하지 않아도 웹프로그래밍을 하고 있다면 자연스럽게 멀티스레딩 환경에 있다는 것은 앞선 포스팅에서 이미 설명했고, 그리고 제가 관심을 가지고 있는 것이 바로 웹프로그래밍이기 때문입니다.😄

    리소스 공유

    앞서 설명한 내용을 최종적으로 정리해보겠습니다. 리소스란 말 그대로 프로그램에서 활용할 수 있는 자원을 말합니다. 현실적으로는 메모리 혹은 메모리에 올라가는 프로그램의 요소들을 모두 '리소스'라고 통칭하는 것 같습니다.
    Java는 Java Virtual Machine(JVM) 위에서 실행됩니다. Virtual Machine. JVM 또한 하나의 프로세스로 실행되는 것이면서, 그 내부에 마치 하나의 머신과도 같은 구조를(정확히는 JVM 스펙을) 구현하고 있는 가상의 장치 (비슷한) 것이죠.
    이런 점을 고려했을 때 실제 머신과 JVM 사이에 차이는 분명 존재하지만, 그럼에도 공통적으로 설명할 수 있는 말이 존재합니다. "공유되는 리소스가 존재한다"는 것이죠. 공유되는 리소스란 Heap(그리고 Data segment 또는 Method area, 그러나 이하 Heap으로 설명하게 될 영역)에 포함되는 아래의 자원들입니다.

    • 객체들(Objects)
    • 클래스 변수, 멤버 변수
    • 배열

    만약에 위의 설명이 잘 와닿지 않는다고 해도 이것만큼만 기억하시면 되겠습니다.

    머신의 메모리에는 "공유되는 리소스가 존재한다". 그리고 해당 리소스는 객체나 객체에 속하는 클래스, 멤버 변수다.

    원자성Atomicity과 임계 영역Critical Section

    원자성과 임계영역 부분은 코드로 설명하지 않겠습니다. 왜냐면 저는 '원자성과 임계영역'이라는 주제를 이해하기 전에는, 그것을 설명하는 코드만 보고 시원하게 이해한 적이 없기 때문입니다. 거꾸로 '원자성과 임계영역'이라는 주제를 개념적으로 이해하고 난 뒤에야 그것을 구현한 코드를 보고 일어날 문제를 예측할 수 있게 되었습니다. 단지 제가 덜 영리한 걸지도 모르지만, 저는 일단 최대한 쉽게 설명을 해볼게요.

    여러 스레드가 스케줄링되어 일하는 것은 '대결'에 비유할 만한 부분이 있습니다. 저는 롤을 하지 않지만 많은 분들이 즐기시는 게임이니까 롤로 예를 들어볼게요.
    'CS 많이 먹기 대결' 같은 건 어떨까요? A와 B, 두 캐릭터가 있다고 해볼게요. A와 B는 열 번을 공격해야 미니언을 죽일 수 있는, 동일한 공격력과 공격속도를 가지고 있습니다. A가 먼저 미니언을 치기 시작했다고 A가 먼저 CS를 먹으란 법이 있을까요?

    • 만약에 미니언이 한 대만 맞아도 죽는 녀석이라면 A가 먼저 공격을 날리는 것을 보자 마자 결과를 '예측' 할 수 있었겠죠, 하지만 미니언은 열 대를 쳐야 죽죠... 그 이후에는 치열한 경쟁이 이어질 겁입니다.
    • A가 미니언을 한 대도 못 치고 있는 상황에서 B는 두 대 칠 수도 있습니다.
    • 그 다음에 B가 한 대 더 치는 동안 A는 미니언을 세 대 칠 수도 있죠.
      위에 나열한 세 가지의 현상을 '치열한 CS 경쟁' 현상이라고 부를 수 있을 것 같아요. 누가 시작했냐 보다는 과정에 따라서 엎치락 뒤치락 할 것이기 때문에 '예측'하기는 어렵겠죠. 만약에 A와 B의 공격력이 매우 강해서 미니언이 한 방 컷이라면, 엎치락 뒤치락 하는 과정 자체가 생기지 않았을 거에요. 다시 말해 치열한 CS 경쟁 현상의 결과는 미니언이 한 방 컷이 아닌 경우엔 결과를 '예측'할 수 없게 됩니다.

    만약에 어떤 사람에게 A가 미니언을 공격하는 화면 부분만 조그맣게 잘라서 보여주고, B가 미니언을 공격하는 화면 부분도 조그맣게 잘라서 보여준다고 해볼게요.
    A와 B가 치열한 CS 경쟁을 시작하는데, A가 미니언을 세 대 쯤 먼저 치기 시작하는 것을 보여주었습니다. 누가 CS를 먼저 먹을 수 있을 것인지 '예상'하라고 하면, 아마 A일 거라고 '예상'하는 경우가 많을 거에요.

    하지만 이 예상에도 두 가지가 간과되어 있습니다.
    첫째로는 앞서 말한 것처럼, A가 먼저 쳤다고 계속해서 B보다 빠르게 미니언의 체력을 깎으란 보장이 없습니다. 치열한 CS 경쟁 현상이죠.
    둘째로는... A가 아무리 빨리 시작하고 많이 체력을 깎았더라도, A와 B가 사실 미친 트롤 녀석들이라서 한 팀이라면요? 하나의 미니언을 두고서 이 짓을 하고 있다면? 치열한 CS 경쟁 현상의 승자는 누가 될까요? 누구긴 누구겠어요. 막타 친 사람입니다. 딱 한 번만 때렸어도 B가 막타를 쳤으면 CS는 B가 먹게 됩니다. 이것을 '미니언 공유'로 인한 결과라고 할 수 있을 것 같아요.

    자... 여기까지의 설명으로 '동일한 공격력과 동일한 공격 속도'라는 전제만으론 결과를 예측할 수 없다는 걸 알 수 있었습니다.

    미니언을 '한 방 컷' 할 수 없다면, '치열한 CS 경쟁 현상' 때문에 결과를 '예측'하기가 쉽지 않습니다.
    그조차 각자가 다른 미니언을 치고 있다고 가정해야 '예상'이나마 가능한 것이지,
    '미니언 공유 상태'라면 예상 조차 불가능한 완전히 랜덤한 결과를 얻을 거라는 말입니다.

    그러면 이제 롤 예시에서 다시 우리의 친근한 컴퓨터 용어로 돌아가 보겠습니다.
    '한 방 컷'이라는 조건은 원자성에 해당합니다. 하나의 수행이 하나의 동작으로 온전히 이루어져서, 시작했다면 어떠한 결과가 보장되는 성질을 말해요.
    '미니언 공유' 상태는 리소스 공유 상태를 말하고요.
    '치열한 CS 경쟁' 현상과 '미니언 공유'로 인한 예측 불가한 현상이 일어나는 걸 프로그램에서는 경쟁 상태(또는 경합 상태, Race Condition)이라고 합니다.
    또한 이렇게 Race Condition이 일어나는 프로그램의 지점을 임계 영역(또는 임계 구역, Critical Section)이라고 합니다.

    우리 곁의 임계영역

    Java와 같은 고수준의 언어에서는 하나의 연산이 실제로도 하나의 동작으로 이루어지는 것처럼 보이는 경우가 있습니다. 예를 들어서 number++와 같은 경우죠. number++ 하면 바로 number에 1이 더해진다는 동작이 된다고 생각되기가 쉽습니다. 마치 미니언이 한 대만 쳐도 죽는 것처럼, 바로 1이 더해지는 건 원자성이 보장되는 것처럼 생각되는 것이죠. 그냥 number에 ++ 한 거니까!

    하지만 실상은 다릅니다.

    1. number의 값을 읽는다
    2. '1.'에서 읽어온 값에 1을 더한다.
    3. '2.'에서 연산한 결과를 number에 할당한다.

    number++는 이렇게 세 가지 과정을 거쳐서 결과를 도출합니다. 한 방 컷이 아니에요! '과정'이 존재하죠. 즉, 기본적으로 원자성이 보장되는 행위가 아니고, 원자성을 보장하기 위해서는 별도의 작업을 필요로 하는 '임계 영역'입니다. 얼마든지 '경쟁 상태'에 빠질 수 있는 코드에요. 특히나 저희가 사랑하는 웹프로그래밍에서는 아주 당연하고도 쉽게 말이죠.
    이것이 java 웹프로그래밍을 처음 배울 때에 '필드 사용을 주의하라'고 계속해서 강조를 듣게 되는 이유입니다. 웹어플리케이션의 많은 요청은 대개 다수의 스레드가 병렬로 처리하게 되니까요. 이에 대비한 프로그래밍을 하지 않으면 우리가 전혀 원치 않은 결과를 얻게 될 지도 모릅니다.

    이에 대비하는 구체적인 방법은...
    다음 포스팅 부터 이어서 알아보겠습니다! 😇

    댓글

Designed by Tistory.