Skip to content
New issue

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

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

Already on GitHub? # 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.

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

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

Successfully merging this pull request may close these issues.

2 participants