티스토리 뷰
😪 잠자는 이발사 문제란?
'잠자는 이발사 문제'는 운영체제의 프로세스 간 통신과 그들의 동기화 문제를 직관적으로 설명하기 위한 문제입니다.
잠자는 이발사 문제는 다음과 같이 정의됩니다.
이발사: 이발사는 고정된 개수의 대기석이 있는 바버샵으로 출근합니다. 대기 중인 손님이 있다면 손님을 이발 의자에 앉혀서 이발을 해 주고, 대기 중인 손님이 없다면 이발사는 낮잠을 잡니다.
손님: 손님은 이발을 받으러 바버샵에 갑니다. 이발사가 다른 손님을 이발하고 있다면 대기석으로 이동합니다. 대기석이 비어있으면 빈자리에 앉아서 기다리고, 그렇지 않다면 바버샵을 나갑니다. 이발을 받을 차례가 된 손님은 이발 의자로 이동하며 이발사가 자고 있다면 이발사를 깨웁니다.
문제: 이발사와 손님의 행동 시간이 확실하게 구분되어 있지 않고 겹치는 문제가 발생할 수 있습니다. 예를 들어, 손님은 이발사가 이발을 하고 있는 것을 확인하고 대기석으로 이동하고 있는데, 그 사이에 이발을 마친 이발사가 비어 있는 대기석을 먼저 확인하고 낮잠을 잘 수 있습니다. 결국 이발사가 자고 있기 때문에 어떤 손님도 이발을 받을 수 없게 됩니다.
📝 문제 풀이
잠자는 이발사 문제를 해결하기 위해서는 다음과 같은 조건이 만족되어야 합니다.
- 한 번에 한 명만 동작 상태를 바꾸거나 읽을 수 있어야 합니다.
- 손님들이 자고 있는 이발사를 깨울 수 있어야 합니다. 그렇지 못하는 경우, 대기석에서 무한정 대기하게 됩니다.
- 대기석에서 무한정 기다리다가 기아 상태에 빠지는 손님이 없어야 합니다.
이 조건들을 만족시키기 위해 다음과 같은 go의 동기화 메커니즘을 활용할 수 있습니다.
- sync.Mutex: 공유 자원의 상태를 변경하거나 읽을 때 다른 스레드나 프로세스가 동시에 접근하는 것을 제한할 수 있습니다.
- channel, select: channel을 사용해 큐의 동작과 비슷하게 손님들을 순서대로 받고, 먼저 온 순서대로 이발을 받을 수 있게 하여 기아 상태에 빠지는 손님이 없게 할 수 있습니다. 특히, buffered channel을 사용하여 고정된 크기의 대기석을 구현할 수 있습니다. 그리고 select문을 사용하여 이발사가 channel을 통해 손님을 받게 함으로써 channel은 (이발사는 자고 있는데도) 손님을 비어있는 이발 의자로 안내해 주는 역할 또한 할 수 있습니다.
1. 손님
// 손님 타입 (문자열 커스텀 타입)
type Customer string
// 손님 생성자
func NewCustomer(name string) *Customer {
customer := Customer(name)
return &customer
}
// 손님의 이름을 문자열로 반환합니다. (Stringer 인터페이스 구현)
func (c Customer) String() string {
return string(c)
}
// 손님이 바버샵에 들어갑니다.
func (c *Customer) EnterBarberShop(shop *BarberShop) {
color.Green("%s(이)가 바버샵에 도착했습니다.\n", c)
shop.ServeCustomer(c) // 바버샵에 손님을 추가합니다.
}
// 손님이 바버샵에서 나갑니다. (이발을 받았는지 여부, 이유)
func (c *Customer) LeaveBarberShop(haircut bool, reasons ...string) {
if !haircut {
comment := "%s(이)가 집에 돌아갑니다."
if len(reasons) > 0 {
comment += fmt.Sprintf("사유: [%s]", reasons[0])
}
color.Red(comment, c)
return
}
color.Green("%s(이)가 이발을 받고 바버샵을 나갑니다.\n", c)
}
// 손님이 이발사를 깨웁니다.
func (c *Customer) WakeBarberUp(barber *Barber) {
color.Green("%s(이)가 %s(을)를 깨웁니다.\n", c, barber.Name)
barber.WakeUp()
}
손님은 별도의 동기화 메커니즘을 사용하지 않고 string 타입을 사용한 커스텀 타입으로 정의하였습니다.
손님은 다음 세 가지 동작을 수행합니다.
- 바버샵에 들어갑니다.
- 바버샵에서 나갑니다.
- 이발사를 깨웁니다.
2. 이발사
type BarberState uint8
const (
Checking BarberState = iota
Cutting
Sleeping
)
// 이발사 구조체
type Barber struct {
Name string // 이발사의 이름
CuttingDuration time.Duration // 이발사가 머리를 깍는데 걸리는 시간
State BarberState // 이발사의 상태
readyToGoHomeChan chan bool // 이발사가 퇴근하기 위해 준비하는 채널
doneChan chan bool // 이발사가 퇴근할 때까지 기다리는 채널
mu sync.Mutex // 이발사의 상태를 변경할 때 사용하는 뮤텍스
}
// 이발사 생성자
func NewBarber(name string, cuttingDuration time.Duration) *Barber {
return &Barber{
Name: name,
CuttingDuration: cuttingDuration,
State: Checking,
readyToGoHomeChan: make(chan bool, 1),
doneChan: make(chan bool, 1),
mu: sync.Mutex{},
}
}
// 이발사가 바버샵에 출근합니다.
func (b *Barber) GoToWork(shop *BarberShop) {
b.mu.Lock()
defer b.mu.Unlock()
color.Magenta("%s(은)는 출근합니다.\n", b.Name)
customers := shop.AddBarber(b) // 바버샵에 이발사를 추가합니다.
if customers == nil {
color.Red("%s(은)는 출근하지 못했습니다. 바버샵이 문을 닫았습니다.\n", b.Name)
return
}
color.Magenta("%s(은)는 바버샵에서 일을 시작합니다.\n", b.Name)
go b.acceptCustomers(customers) // 바버샵에 있는 손님들을 받아서 일을 합니다.
}
// 바버샵에 있는 손님들을 받아서 일을 합니다.
func (b *Barber) acceptCustomers(customers <-chan *Customer) {
for {
select {
case <-b.readyToGoHomeChan: // 퇴근을 준비합니다.
b.GoHome()
return
case customer, ok := <-customers: // 바버샵에 있는 손님들을 받습니다.
// 바버샵에 있는 손님들을 받았는데, 바버샵이 문을 닫았거나 손님이 없으면 다음 손님을 받습니다.
if !ok || customer == nil {
continue
}
// 손님을 받았는데, 이발사가 자고 있으면 손님이 이발사를 깨우고, 이발사의 상태를 체크 중으로 변경합니다.
if b.IsSleeping() {
customer.WakeBarberUp(b)
}
b.CutHair(customer) // 이발사가 손님의 머리를 깍습니다.
default:
b.mu.Lock()
// 대기 중인 손님이 없으면 이발사가 잠을 잡니다.
if b.State == Checking {
color.Magenta("%s(은)는 할 일이 없어 잠을 잡니다.\n", b.Name)
b.State = Sleeping
}
b.mu.Unlock()
}
}
}
// 이발사가 손님의 머리를 깍습니다.
func (b *Barber) CutHair(customer *Customer) {
// 머리를 깍습니다.
b.mu.Lock()
b.State = Cutting
b.mu.Unlock()
color.Magenta("%s(은)는 %s의 머리를 깍습니다.\n", b.Name, customer)
time.Sleep(b.CuttingDuration)
color.Magenta("%s(은)는 %s의 머리를 다 깍았습니다.\n", b.Name, customer)
customer.LeaveBarberShop(true) // 손님이 이발을 받고 바버샵을 나갑니다.
// 머리를 다 깍았으면 다음 손님을 받습니다.
b.mu.Lock()
b.State = Checking
b.mu.Unlock()
}
// 이발사가 자는지 확인합니다.
func (b *Barber) IsSleeping() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.State == Sleeping
}
// 이발사가 잠에서 깨어납니다.
func (b *Barber) WakeUp() {
b.mu.Lock()
defer b.mu.Unlock()
b.State = Checking
}
// 이발사가 퇴근 알림을 받습니다.
func (b *Barber) GoodToGoHome() {
b.readyToGoHomeChan <- true // 이발사가 퇴근할 준비를 합니다.
}
// 이발사가 퇴근합니다.
func (b *Barber) GoHome() {
defer func() {
close(b.doneChan)
close(b.readyToGoHomeChan)
}()
color.Magenta("%s(은)는 퇴근을 준비합니다.\n", b.Name)
time.Sleep(time.Millisecond * 3000)
color.Magenta("%s(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.\n", b.Name)
}
// 이발사가 퇴근할 때까지 기다립니다.
func (b *Barber) Done() <-chan bool {
return b.doneChan
}
이발사는 이름, 이발하는 데 걸리는 시간, 상태, 채널 그리고 뮤텍스를 가진 Barber 구조체로 정의하였습니다. 여기서 readyToGoHome 채널은 바버샵의 영업시간이 끝나면 이발사가 퇴근을 할 수 있도록 알림을 주고받기 위해 추가하였으며, done 채널은 이발사가 완전히 퇴근한 것을 바버샵에 알리기 위해 추가하였습니다.
이발사는 다음 동작들을 수행합니다.
- 바버샵에 출근합니다. 출근과 동시에 손님들을 받습니다.
- 손님을 받아서 이발을 합니다. 이발사가 자고 있다면 손님이 이발사를 깨웁니다.
- 대기석에 손님이 없으면 낮잠을 잡니다.
- 퇴근 알림을 받으면 퇴근합니다.
여기서 2, 3, 4번은 select문 안에서 한 번에 하나씩 동기적으로 실행됩니다.
3. 바버샵
type BarberShop struct {
Capacity int // 바버샵의 대기석 수용 인원
OpenDuration time.Duration // 바버샵의 영업 시간
barbers []*Barber // 바버샵에 있는 이발사들
customerChan chan *Customer // 바버샵에 있는 손님들의 채널
Open bool // 바버샵이 영업 중인지 여부
wg sync.WaitGroup // 바버샵의 모든 이발사들이 퇴근할 때까지 기다리기 위한 WaitGroup
mu sync.RWMutex // 바버샵의 상태를 변경할 때 사용하는 뮤텍스
}
func NewBarberShop(capacity int, openDuration time.Duration) *BarberShop {
return &BarberShop{
Capacity: capacity,
OpenDuration: openDuration,
barbers: make([]*Barber, 0),
customerChan: make(chan *Customer, capacity),
Open: false,
wg: sync.WaitGroup{},
mu: sync.RWMutex{},
}
}
func (b *BarberShop) OpenShop() {
b.mu.Lock()
defer b.mu.Unlock()
b.Open = true // 바버샵을 오픈합니다.
color.Blue("공지: 바버샵이 문을 열었습니다. 영업 시간은 %s입니다.\n", b.OpenDuration)
// goroutine을 사용하여 영업 시간이 끝나면 바버샵을 닫습니다.
go func() {
timer := time.NewTimer(b.OpenDuration)
<-timer.C // 영업 시간 타이머가 끝나면 바버샵을 닫습니다.
b.CloseShop()
}()
}
func (b *BarberShop) CloseShop() {
b.mu.Lock()
defer b.mu.Unlock()
b.Open = false // 바버샵을 닫습니다.
color.Blue("공지: 영업 시간이 종료되었습니다. 대기 중인 손님들을 모두 돌려보냅니다.\n")
// 바버샵에 있는 모든 손님들을 돌려보냅니다.
for len(b.customerChan) > 0 {
customer := <-b.customerChan
customer.LeaveBarberShop(false, "바버샵의 영업 시간이 종료되었습니다.")
}
close(b.customerChan)
color.Blue("공지: 모든 손님들을 돌려보냈습니다. 이발사들을 퇴근시킵니다.\n")
// 바버샵에 있는 모든 이발사들을 퇴근시킵니다.
for _, barber := range b.barbers {
barber.GoodToGoHome() // 이발사들이 퇴근할 준비를 합니다.
go func(barber *Barber) {
<-barber.Done() // 이발사들이 퇴근할 때까지 기다립니다.
b.wg.Done()
}(barber)
}
}
func (b *BarberShop) IsOpen() bool {
b.mu.RLock()
defer b.mu.RUnlock()
return b.Open
}
func (b *BarberShop) AddBarber(barber *Barber) <-chan *Customer {
b.mu.Lock()
defer b.mu.Unlock()
// 바버샵이 닫혀있으면 이발사를 추가할 수 없습니다.
if !b.Open {
return nil
}
// 이발사를 추가하고 손님들을 받아들일 채널을 반환합니다.
b.barbers = append(b.barbers, barber)
b.wg.Add(1)
return b.customerChan
}
func (b *BarberShop) ServeCustomer(customer *Customer) {
b.mu.Lock()
defer b.mu.Unlock()
// 바버샵이 닫혀있으면 손님을 추가할 수 없습니다.
if !b.Open {
customer.LeaveBarberShop(false, "바버샵이 문을 닫았습니다.")
return
}
select {
case b.customerChan <- customer: // 바버샵에 손님을 추가합니다.
default: // 바버샵이 꽉 찼으면 손님을 추가할 수 없습니다.
customer.LeaveBarberShop(false, "바버샵이 꽉 찼습니다.")
}
}
func (b *BarberShop) WaitTilAllDone() {
b.wg.Wait() // 바버샵의 모든 이발사들이 퇴근할 때까지 기다립니다.
color.Blue("공지: 모든 이발사가 퇴근했습니다. 바버샵이 문을 닫습니다. 다음에 또 오세요!\n")
}
바버샵은 대기석 인원, 영업 시간, 이발사들, 손님을 받는 채널 등으로 구성된 BarberShop 구조체로 정의하였습니다.
바버샵은 다음 동작들을 수행합니다.
- 샵을 엽니다. 영업 시간이 지나면 샵을 닫습니다.
- 이발사를 추가합니다.
- 손님을 받습니다.
- 모든 이발사가 퇴근할 때까지 기다립니다. 그리고 완전히 문을 닫습니다.
4. 무작위 손님 생성
func randomCustomers(shop *BarberShop, arrivalRate int) {
customerId := 1
for {
if !shop.IsOpen() {
return
}
randMillisecond := rand.Int() % arrivalRate
<-time.After(time.Duration(randMillisecond) * time.Millisecond) // 랜덤한 시간 동안 기다립니다.
NewCustomer(fmt.Sprintf("손님%d", customerId)).EnterBarberShop(shop) // 바버샵에 손님을 추가합니다.
customerId++ // 손님 ID를 증가시킵니다.
}
}
바버샵이 영업을 하는 동안 무작위 시간에 손님을 한 명씩 바버샵으로 보냅니다.
5. SleepingBarber 함수
func SleepingBarber(barbers []struct {
name string
cuttingDuration time.Duration
}, capacity, arrivalRate int) {
shop := NewBarberShop(capacity, time.Duration(time.Second)*10) // capacity 만큼의 손님을 수용할 수 있고 10초 동안 영업하는 바버샵을 만듭니다.
shop.OpenShop() // 바버샵을 오픈합니다.
// 바버샵에 이발사들을 추가합니다.
for _, barber := range barbers {
NewBarber(barber.name, barber.cuttingDuration).GoToWork(shop)
}
go randomCustomers(shop, arrivalRate) // 랜덤한 시간 간격으로 손님들이 바버샵에 들어갑니다.
shop.WaitTilAllDone() // 바버샵이 문을 닫고 모든 이발사들이 퇴근할 때까지 기다립니다.
}
capacity 만큼의 대기석을 가지고 있고 10초 동안 영업하는 바버샵을 생성합니다. 그리고 이발사의 (이름, 이발 시간) 쌍을 받아서 이발사를 생성하고 출근시킵니다. 마지막으로 무작위 손님을 생성하여 바버샵으로 보냅니다.
6. main 함수
func main() {
fmt.Println("잠자는 이발사 문제")
fmt.Println("==================================================")
barbers := []struct {
name string
cuttingDuration time.Duration
}{
{"철수", 1000 * time.Millisecond},
{"영희", 2000 * time.Millisecond},
{"영수", 3000 * time.Millisecond},
{"민수", 2000 * time.Millisecond},
{"민희", 3000 * time.Millisecond},
{"국봉", 1000 * time.Millisecond},
}
SleepingBarber(barbers, 10, 400) // 잠자는 이발사 문제를 해결합니다.
fmt.Println("==================================================")
}
SleepingBarber 함수를 실행하는데 필요한 인수를 정의하여 함수를 실행합니다.
7. main 함수 실행 결과
$ go run -race .
잠자는 이발사 문제
==================================================
공지: 바버샵이 문을 열었습니다. 영업 시간은 10s입니다.
철수(은)는 출근합니다.
철수(은)는 바버샵에서 일을 시작합니다.
영희(은)는 출근합니다.
영희(은)는 바버샵에서 일을 시작합니다.
영수(은)는 출근합니다.
영희(은)는 할 일이 없어 잠을 잡니다.
영수(은)는 바버샵에서 일을 시작합니다.
철수(은)는 할 일이 없어 잠을 잡니다.
민수(은)는 출근합니다.
민수(은)는 바버샵에서 일을 시작합니다.
민희(은)는 출근합니다.
영수(은)는 할 일이 없어 잠을 잡니다.
민수(은)는 할 일이 없어 잠을 잡니다.
민희(은)는 바버샵에서 일을 시작합니다.
국봉(은)는 출근합니다.
국봉(은)는 바버샵에서 일을 시작합니다.
국봉(은)는 할 일이 없어 잠을 잡니다.
민희(은)는 할 일이 없어 잠을 잡니다.
손님1(이)가 바버샵에 도착했습니다.
손님1(이)가 영수(을)를 깨웁니다.
영수(은)는 손님1의 머리를 깍습니다.
손님2(이)가 바버샵에 도착했습니다.
손님2(이)가 민희(을)를 깨웁니다.
민희(은)는 손님2의 머리를 깍습니다.
손님3(이)가 바버샵에 도착했습니다.
손님3(이)가 국봉(을)를 깨웁니다.
국봉(은)는 손님3의 머리를 깍습니다.
손님4(이)가 바버샵에 도착했습니다.
손님4(이)가 영희(을)를 깨웁니다.
영희(은)는 손님4의 머리를 깍습니다.
손님5(이)가 바버샵에 도착했습니다.
손님5(이)가 민수(을)를 깨웁니다.
민수(은)는 손님5의 머리를 깍습니다.
손님6(이)가 바버샵에 도착했습니다.
손님6(이)가 철수(을)를 깨웁니다.
철수(은)는 손님6의 머리를 깍습니다.
손님7(이)가 바버샵에 도착했습니다.
손님8(이)가 바버샵에 도착했습니다.
국봉(은)는 손님3의 머리를 다 깍았습니다.
손님3(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님7의 머리를 깍습니다.
손님9(이)가 바버샵에 도착했습니다.
손님10(이)가 바버샵에 도착했습니다.
철수(은)는 손님6의 머리를 다 깍았습니다.
손님6(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님8의 머리를 깍습니다.
손님11(이)가 바버샵에 도착했습니다.
손님12(이)가 바버샵에 도착했습니다.
국봉(은)는 손님7의 머리를 다 깍았습니다.
손님7(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님9의 머리를 깍습니다.
영희(은)는 손님4의 머리를 다 깍았습니다.
손님4(이)가 이발을 받고 바버샵을 나갑니다.
영희(은)는 손님10의 머리를 깍습니다.
손님13(이)가 바버샵에 도착했습니다.
민수(은)는 손님5의 머리를 다 깍았습니다.
손님5(이)가 이발을 받고 바버샵을 나갑니다.
민수(은)는 손님11의 머리를 깍습니다.
철수(은)는 손님8의 머리를 다 깍았습니다.
손님8(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님12의 머리를 깍습니다.
영수(은)는 손님1의 머리를 다 깍았습니다.
손님1(이)가 이발을 받고 바버샵을 나갑니다.
영수(은)는 손님13의 머리를 깍습니다.
손님14(이)가 바버샵에 도착했습니다.
민희(은)는 손님2의 머리를 다 깍았습니다.
손님2(이)가 이발을 받고 바버샵을 나갑니다.
민희(은)는 손님14의 머리를 깍습니다.
손님15(이)가 바버샵에 도착했습니다.
국봉(은)는 손님9의 머리를 다 깍았습니다.
손님9(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님15의 머리를 깍습니다.
손님16(이)가 바버샵에 도착했습니다.
손님17(이)가 바버샵에 도착했습니다.
철수(은)는 손님12의 머리를 다 깍았습니다.
손님12(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님16의 머리를 깍습니다.
손님18(이)가 바버샵에 도착했습니다.
손님19(이)가 바버샵에 도착했습니다.
손님20(이)가 바버샵에 도착했습니다.
손님21(이)가 바버샵에 도착했습니다.
국봉(은)는 손님15의 머리를 다 깍았습니다.
손님15(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님17의 머리를 깍습니다.
손님22(이)가 바버샵에 도착했습니다.
영희(은)는 손님10의 머리를 다 깍았습니다.
손님10(이)가 이발을 받고 바버샵을 나갑니다.
영희(은)는 손님18의 머리를 깍습니다.
민수(은)는 손님11의 머리를 다 깍았습니다.
손님11(이)가 이발을 받고 바버샵을 나갑니다.
민수(은)는 손님19의 머리를 깍습니다.
손님23(이)가 바버샵에 도착했습니다.
철수(은)는 손님16의 머리를 다 깍았습니다.
손님16(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님20의 머리를 깍습니다.
손님24(이)가 바버샵에 도착했습니다.
손님25(이)가 바버샵에 도착했습니다.
손님26(이)가 바버샵에 도착했습니다.
국봉(은)는 손님17의 머리를 다 깍았습니다.
손님17(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님21의 머리를 깍습니다.
손님27(이)가 바버샵에 도착했습니다.
손님28(이)가 바버샵에 도착했습니다.
철수(은)는 손님20의 머리를 다 깍았습니다.
손님20(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님22의 머리를 깍습니다.
손님29(이)가 바버샵에 도착했습니다.
영수(은)는 손님13의 머리를 다 깍았습니다.
손님13(이)가 이발을 받고 바버샵을 나갑니다.
영수(은)는 손님23의 머리를 깍습니다.
민희(은)는 손님14의 머리를 다 깍았습니다.
손님14(이)가 이발을 받고 바버샵을 나갑니다.
민희(은)는 손님24의 머리를 깍습니다.
손님30(이)가 바버샵에 도착했습니다.
국봉(은)는 손님21의 머리를 다 깍았습니다.
손님21(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님25의 머리를 깍습니다.
영희(은)는 손님18의 머리를 다 깍았습니다.
손님18(이)가 이발을 받고 바버샵을 나갑니다.
영희(은)는 손님26의 머리를 깍습니다.
민수(은)는 손님19의 머리를 다 깍았습니다.
손님19(이)가 이발을 받고 바버샵을 나갑니다.
민수(은)는 손님27의 머리를 깍습니다.
손님31(이)가 바버샵에 도착했습니다.
손님32(이)가 바버샵에 도착했습니다.
철수(은)는 손님22의 머리를 다 깍았습니다.
손님22(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님28의 머리를 깍습니다.
손님33(이)가 바버샵에 도착했습니다.
국봉(은)는 손님25의 머리를 다 깍았습니다.
손님25(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님29의 머리를 깍습니다.
손님34(이)가 바버샵에 도착했습니다.
손님35(이)가 바버샵에 도착했습니다.
손님36(이)가 바버샵에 도착했습니다.
철수(은)는 손님28의 머리를 다 깍았습니다.
손님28(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님30의 머리를 깍습니다.
손님37(이)가 바버샵에 도착했습니다.
손님38(이)가 바버샵에 도착했습니다.
손님39(이)가 바버샵에 도착했습니다.
국봉(은)는 손님29의 머리를 다 깍았습니다.
손님29(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님31의 머리를 깍습니다.
영희(은)는 손님26의 머리를 다 깍았습니다.
손님26(이)가 이발을 받고 바버샵을 나갑니다.
영희(은)는 손님32의 머리를 깍습니다.
손님40(이)가 바버샵에 도착했습니다.
민수(은)는 손님27의 머리를 다 깍았습니다.
손님27(이)가 이발을 받고 바버샵을 나갑니다.
민수(은)는 손님33의 머리를 깍습니다.
손님41(이)가 바버샵에 도착했습니다.
철수(은)는 손님30의 머리를 다 깍았습니다.
손님30(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 손님34의 머리를 깍습니다.
영수(은)는 손님23의 머리를 다 깍았습니다.
손님23(이)가 이발을 받고 바버샵을 나갑니다.
영수(은)는 손님35의 머리를 깍습니다.
민희(은)는 손님24의 머리를 다 깍았습니다.
손님24(이)가 이발을 받고 바버샵을 나갑니다.
민희(은)는 손님36의 머리를 깍습니다.
손님42(이)가 바버샵에 도착했습니다.
국봉(은)는 손님31의 머리를 다 깍았습니다.
손님31(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 손님37의 머리를 깍습니다.
손님43(이)가 바버샵에 도착했습니다.
손님44(이)가 바버샵에 도착했습니다.
손님45(이)가 바버샵에 도착했습니다.
손님46(이)가 바버샵에 도착했습니다.
공지: 영업 시간이 종료되었습니다. 대기 중인 손님들을 모두 돌려보냅니다.
손님38(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님39(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님40(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님41(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님42(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님43(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님44(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님45(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
손님46(이)가 집에 돌아갑니다.사유: [바버샵의 영업 시간이 종료되었습니다.]
공지: 모든 손님들을 돌려보냈습니다. 이발사들을 퇴근시킵니다.
철수(은)는 손님34의 머리를 다 깍았습니다.
손님34(이)가 이발을 받고 바버샵을 나갑니다.
철수(은)는 퇴근을 준비합니다.
손님47(이)가 바버샵에 도착했습니다.
손님47(이)가 집에 돌아갑니다.사유: [바버샵이 문을 닫았습니다.]
영희(은)는 손님32의 머리를 다 깍았습니다.
국봉(은)는 손님37의 머리를 다 깍았습니다.
손님32(이)가 이발을 받고 바버샵을 나갑니다.
손님37(이)가 이발을 받고 바버샵을 나갑니다.
국봉(은)는 퇴근을 준비합니다.
영희(은)는 퇴근을 준비합니다.
민수(은)는 손님33의 머리를 다 깍았습니다.
손님33(이)가 이발을 받고 바버샵을 나갑니다.
민수(은)는 퇴근을 준비합니다.
영수(은)는 손님35의 머리를 다 깍았습니다.
손님35(이)가 이발을 받고 바버샵을 나갑니다.
영수(은)는 퇴근을 준비합니다.
민희(은)는 손님36의 머리를 다 깍았습니다.
손님36(이)가 이발을 받고 바버샵을 나갑니다.
민희(은)는 퇴근을 준비합니다.
철수(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
영희(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
국봉(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
민수(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
영수(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
민희(은)는 오늘 하루 일을 마치고 집으로 돌아갑니다.
공지: 모든 이발사가 퇴근했습니다. 바버샵이 문을 닫습니다. 다음에 또 오세요!
==================================================
-race 플래그를 사용하였지만 race condition은 감지되지 않고 정상적으로 실행되는 것을 확인할 수 있습니다.
8. 테스트 실행
package main
import (
"math/rand"
"os"
"sync"
"testing"
"time"
)
func TestMain(m *testing.M) {
main()
os.Exit(m.Run())
}
func TestSleepingBarber(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
barbers := []struct {
name string
cuttingDuration time.Duration
}{
{"철수", 0},
{"영희", 0},
{"영수", 0},
{"민수", 0},
{"민희", 0},
{"국봉", 0},
}
go func() {
defer wg.Done()
SleepingBarber(barbers, rand.Intn(10)+1, rand.Intn(300)+200)
}()
}
wg.Wait()
}
SleepingBarber 함수를 100번 병렬로 실행해 보았습니다.
=== RUN TestSleepingBarber
...
--- PASS: TestSleepingBarber (17.71s)
PASS
coverage: 100.0% of statements
ok sleeping-barber 34.047s coverage: 100.0% of statements
main 함수를 실행한 결과와 마찬가지로 race condition은 감지되지 않았습니다.
🙏 마치며
식사하는 철학자들 문제에 이어서 go의 channel을 활용해 잠자는 이발사 문제를 해결해 보았습니다. go는 키워드가 적어서 얼핏 보면 기능이 부족해 보일 수 있지만, 가지고 있는 것들 만으로도 충분히 강력하고 복잡한 코드를 작성할 수 있다고 생각합니다.
다음은 아키텍쳐 또는 디자인 패턴과 관련된 게시물로 돌아오겠습니다.
🐱👤 전체 코드
📖 참고자료
글에서 수정이 필요한 부분이나 설명이 부족한 부분이 있다면 댓글로 남겨주세요!
'Go > 코딩 하기' 카테고리의 다른 글
[Go] gRPC 파헤치기 - gRPC란? (0) | 2023.10.11 |
---|---|
[Go] 문자열과 바이트 슬라이스를 상호 변환하는 여러가지 방법 (0) | 2023.10.01 |
[Go] 식사하는 철학자들 문제 (0) | 2023.09.29 |
[Go] 깃허브 프로필 최신 블로그 글 목록 자동으로 업데이트하기 (0) | 2023.09.06 |
타입스크립트로 블록체인 만들기 in Go (0) | 2023.08.21 |