Udemy-akka-essential # 04 ThreadModelLimitations

Ada·2023년 9월 22일

Akka

목록 보기
24/32

이 강의는 솔직히 완벽히 이해하지 못했다.

그래도 강사님의 말을 정리해보자면
스레드 모델에서 경험할 수 있는 문제점들은 아래와 같다.

  1. 객체 지향의 캡슐화 원칙이 멀티 스레딩 환경에서 깨진다.

  2. 작업을 위임하는 것이 까다롭다.

  3. 멀티 스레딩 또는 분산 환경에서 오류를 추적하고 처리하는 것은 매우 어렵다.

아래는 강사님이 사용하신 예시 코드이다.

package part1recap

import scala.concurrent.Future

object ThreadModelLimitations extends App {

  /**
   * DR #1: 객체 지향 프로그래밍의 캡슐화는 단일 스레드 모델에서만 유효하다.
   */
  class BankAccount(private var amount: Int) {
    override def toString: String = "" + amount

    // 돈을 출금하는 메서드. 동기화하여 동시에 여러 스레드가 접근하지 못하게 함.
    def withdraw(money: Int) = this.synchronized {
      this.amount -= money
    }

    // 돈을 입금하는 메서드. 동기화하여 동시에 여러 스레드가 접근하지 못하게 함.
    def deposit(money: Int) = this.synchronized {
      this.amount += money
    }

    // 계좌의 현재 금액을 확인하는 메서드
    def getAmount = amount
  }

  // 다중 스레드 환경에서 OOP 캡슐화가 깨지는 예시. 
  // 주석 처리된 코드는 2000원을 가진 계좌에서 1000번 출금하고 1000번 입금하는 예제임.
  // 하지만 실제 결과가 2000원이 되지 않을 수 있음.

  //  val account = new BankAccount(2000)
  //  for(_ <- 1 to 1000) {
  //    new Thread(() => account.withdraw(1)).start()
  //  }
  //
  //  for(_ <- 1 to 1000) {
  //    new Thread(() => account.deposit(1)).start()
  //  }
  //  println(account.getAmount)

  // OOP encapsulation is broken in a multithreaded env
  // synchronization! Locks to the rescue

  // deadlocks, livelocks

  // 동기화 (synchronization)를 사용해도 교착상태(deadlocks)나 생명교착(livelocks) 같은 문제가 발생할 수 있음.

  /**
   * DR #2: 스레드에게 어떤 작업을 위임하는 것은 번거롭다.
   */
  // 주어진 스레드에 runnable 작업을 전달하는 예제

  // 현재 실행할 작업을 저장할 변수
  var task: Runnable = null

  // 주어진 task를 계속 실행하는 스레드 생성
  val runningThread: Thread = new Thread(() => {
    while (true) {
      while (task == null) {
        runningThread.synchronized {
          println("[background] 작업을 기다리는 중...")
          runningThread.wait()
        }
      }

      task.synchronized {
        println("[background] 작업을 받았습니다!")
        task.run()
        task = null
      }
    }
  })

  // 주어진 runnable 작업을 runningThread에게 위임하는 함수
  def delegateToBackgroundThread(r: Runnable) = {
    if (task == null) task = r

    runningThread.synchronized {
      runningThread.notify()
    }
  }

  // 스레드를 시작하고 예제 작업을 실행
  runningThread.start()
  Thread.sleep(1000)
  delegateToBackgroundThread(() => println(42))
  Thread.sleep(1000)
  delegateToBackgroundThread(() => println("이것은 백그라운드에서 실행되어야 합니다."))

  /**
   * DR #3: 다중 스레드 환경에서 오류를 추적하고 처리하는 것은 어렵다.
   */
  // 10개의 스레드로 1백만 숫자를 합산하는 예제

  // 스칼라의 Future를 사용하여 비동기 작업을 처리
  import scala.concurrent.ExecutionContext.Implicits.global

  val futures = (0 to 9)
    .map(i => BigInt(100000 * i) until BigInt(100000 * (i + 1))) // 0-99999, 100000-199999, 200000-299999 등의 숫자 범위 생성
    .map(range => Future {
      if (range.contains(546735)) throw new RuntimeException("유효하지 않은 숫자")
      range.sum
    })

  // 모든 숫자의 합을 구하는 Future 생성
  val sumFuture = Future.reduceLeft(futures)(_ + _)
  sumFuture.onComplete(println)
}
profile
백엔드 프로그래머

0개의 댓글