Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

은행창구 매니저 [Step3] 토이, 쥬봉이 #330

Open
wants to merge 9 commits into
base: ic_10_toy123
Choose a base branch
from

Conversation

jyubong
Copy link

@jyubong jyubong commented Nov 22, 2023

@1Consumption
올라프 늦었지만 Step3 PR 보냅니다💪
비동기처리를 하는 부분이 어려웠습니다.
GCD, Operation 등 열심히 공부해보았고 지금도 하고 있습니다!
이번 스텝도 잘부탁드립니다.


구현 화면

처음 실행 두번째 실행 후 종료까지
gcd1 gcd2

고민이 되었던 점

  1. 예금 은행원 2명, 대출 은행원 1명 구현하는 방법
  • Bank에서는 [Baking: BankClerk] dictionary로 bankClerks를 구현하였습니다. 이는 키값(고객의 banking 업무)에 맞는 value(BankClerk 인스턴스)를 가져오기 위해서입니다.
  • 대출 은행원은 serial queue로 구현하여 차례로 대출업무가 수행되도록 구현하였습니다.
let loanQueue = DispatchQueue(label: "loanQueue")

loanQueue.async(group: group) {
    bankClerk[banking]?.receive(customer: customer)
}
  • 예금 은행원 2명을 설정하는 방식에서 고민이 많았습니다. DispatchSemaphore의 value를 2로 설정해, customer로 접근할 수 있는 예금 은행원 스레드 수를 제어하는 방식으로 구현하였습니다.
let semaphore = DispatchSemaphore(value: 2)
let depositQueue = DispatchQueue(label: "depositQueue", attributes: .concurrent)

depositQueue.async(group: group) {
    semaphore.wait()
    bankClerk[banking]?.receive(customer: customer)
    semaphore.signal()
}
  1. DispatchGroup 활용
  • DispatchQueue에 async로 보낼경우 제어권을 호출한 스레드로 바로 반환하게 됩니다. 그러면 아래 사진처럼 현재 스레드는 비동기 작업에 상관없이 time을 체크하고 close() 메서드를 부르게 됩니다.
    image
  • 이를 제어하기 위해 custom Queue들을 group으로 묶어주어 이 group이 끝날때까지 현재 스레드가 기다리도록 group.wait()을 사용하였습니다.

조언을 구하고 싶은 부분

  1. 프로젝트에서는 GCD로 구현을하였습니다. GCD구현이 적절했는지 그리고 이 외에 Operation으로도 구현해보았는데 이부분도 적절한지 조언을 얻고 싶습니다. (실행에는 이상이 없습니다.)
struct Bank: BankBusinesable {
    ...
    private let bankClerks = [Banking.deposit: BankClerk(charge: .deposit), Banking.loan: BankClerk(charge: .loan)]
    private let depositCounter: OperationQueue = {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2

        return queue
    }()
    
    private let loanBankCounter: OperationQueue = {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        
        return queue
    }()
    
    ...
    
    func open() {
        
        bankManager.assignCustomer(depositCounter: depositCounter, loanCounter: loanBankCounter, bankClerks: bankClerks)
        depositCounter.waitUntilAllOperationsAreFinished()
        loanBankCounter.waitUntilAllOperationsAreFinished()
        
        ...
    }
    
    ...
}
public struct BankManager<BankClerk: CustomerReceivable> {
    public func assignCustomer(depositCounter: OperationQueue, loanCounter: OperationQueue, bankClerks: [Banking: BankClerk]) {
        while let customer = customerQueue.dequeue() as? BankClerk.Customer, let banking = customer.banking {
            let operation = BlockOperation {
                bankClerks[banking]?.receive(customer: customer)
            }

            switch banking {
            case .deposit:
                depositCounter.addOperation(operation)
            case .loan:
                loanCounter.addOperation(operation)
            }
        }
    }
}
  • 은행원 수 제한을 위해 maxConcurrentOperationCount을 사용하였습니다.
  • operaionQueue의 모든 작업이 끝날때까지 현재 스레드는 기다려야하기때문에 waitUntilAllOperationsAreFinished()을 호출하였습니다.
  • BankManager의 assignCustomer메서드는 operationQueue와 bankClerk을 매개변수로 받아 내부에서 operation을 구현하고, 각각의 큐에 addOperation 해주었습니다.
  • Bank 전체코드, BankManager 전체코드
  1. 비동기처리 부분이 많이 미숙합니다. 많이 배우겠습니다!!

Copy link

@1Consumption 1Consumption left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨어요.
비동기 처리가 많이 어려우셨을텐데, 잘 해주셨어요.
OperationQueue로도 잘 구현을 해주셨네요.

스텝이 작기도하고, 충분히 고민을 많이 해주신 것 같아서 코멘트는 없습니다.
다만 질문이 하나 있어요.

Q: 혹시 언제 OperationQueue를 사용해야만 할까요? DispatchQueue보다 OperationQueue가 더 제공하는 점은 어떤게 있을까요?

Comment on lines +12 to +31
public func assignCustomer(to bankClerk: [Banking: BankClerk]) {
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 2)
let depositQueue = DispatchQueue(label: "depositQueue", attributes: .concurrent)
let loanQeueue = DispatchQueue(label: "loanQueue")

while let customer = customerQueue.dequeue() as? BankClerk.Customer,
let banking = customer.banking {
switch banking {
case .deposit:
depositQueue.async(group: group) {
semaphore.wait()
bankClerk[banking]?.receive(customer: customer)
semaphore.signal()
}
case .loan:
loanQeueue.async(group: group) {
bankClerk[banking]?.receive(customer: customer)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번에 최대 실행될 수 있는 작업의 수는 3개인데, 생성된 쓰레드의 수는 3개보다 훨씬 많네요.
image

총 3개의 쓰레드만 생성하고싶다면 어떻게 해야할까요?

Comment on lines +8 to +11
public enum Banking: String, CaseIterable {
case deposit = "예금"
case loan = "대출"
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomStringConvertible이라는 프로토콜을 사용해도 좋을 것 같아요.

@@ -1,3 +1,4 @@
public protocol CustomerNumbering {
var number: UInt { get }
var banking: Banking? { get }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 옵셔널일 필요가 있을까요? randomElement()가 옵셔널을 반환한다면, 기본값을 지정해줘도 좋을 것 같아요.

Comment on lines +11 to +19
private let work: Banking
private var pace: Double {
switch work {
case .deposit:
return 0.7
case .loan:
return 1.1
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 이 정보가 은행원보다는 업무에 들어가야한다고 생각해요. 현재 은행원에 따라 속도차이가 나는게 아니라, 업무에 따라 속도 차이가 나기 때문이에요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants