프로젝트-고루틴으로 배송하기

프로젝트-고루틴으로 배송하기

고루틴으로 배송하기에 대해 알아보자.


배송 상태 확인(1)


이 기능은 상품이 주문접수가 된 시점에서 "주문접수", "배송중", "배송완료" 상태를 보여줍니다. 이는 각 주문 건수마다 독립적으로 진행되는 시간이므로 고루틴과 채널을 활용하기에 아주 좋은 기능입니다.
여기서 주의해야 할 것은 주문하는 물품의 종류가 배송할 수 있는 건수가 아니라 한 개의 주문이 배송 건수입니다. 예를 들어, 장바구니에 롱패딩 5개, 빼빼로2개를 담고 주문하면 두개의 물품이 한 배송으로 처리되는 것입니다. 배송마다 비동기적으로 프로세스를 진행하기 때문에 고루틴을 사용하고 배송 시작과 완료를 송/수신하기 위해 채널을 사용합니다.

앞으로 진행할 내용은 배송 구조체, 기능을 구현하는 것입니다. 우선 주문 건수를 제한하는 변수를 만들어 주문 건수를 5개로 한정합니다.

func main () {
	numbuy := 0 // 주문한 개수
	.....

주문 기능은

buying(items, buyer, itemchoice, &numbuy) // buying() 함수로 바로 주문할 경우
bucketBuying(items, buyer, &numbuy) // bucketBuying() 함수로 장바구니 물품을 주문할 경우

가 있습니다. 다른 함수들에서 한 변수의 숫자를 증가시키므로 포인터 변수로 매개변수를 추가합니다. 함수의 매개변수로 num *int을 추가했습니다.

주문을 하는 곳에 numbuy가 5이하일 경우만 주문할 수 있도록 합니다. 주문하면 배송 횟수를 1 증가시킵니다.

// buying() 함수
if buy == 1 { // 바로 구매		
	if *num < 5 {
		itm[itmchoice-1].amount -= inputamount
		byr.point -= itm[itmchoice-1].price * inputamount

		fmt.Println("상품이 주문 접수 되었습니다.")
		
		*num++
		
		break
	} else {
		fmt.Println("배송 한도를 초과했습니다. 배송이 완료되면 주문하세요.")
		break
	}
}

// bucketBuying 함수
if *num < 5 {
	for index, val := range byr.shoppingbucket {
		for i := range itm {
			if itm[i].name == index {
				itm[i].amount -= val // 수량 차감
				byr.point -= itm[i].price * val // 포인트 차감
			}
		}
	}

	byr.shoppingbucket = map[string]int{}  // 장바구니 초기화
	*num++ // 주문 건수 증가
	fmt.Println("주문 접수 되었습니다.")
} else {
	fmt.Println("배송 한도를 초과했습니다. 배송이 완료되면 주문하세요.")
}

배송 상태 확인(2)


배송에 대한 구조체를 만들기 위해 구체화를 해보겠습니다. 이 택배 트럭의 정보를 가지는 구조체를 만듭니다. 이 구조체는 두 개의 필드 값을 가집니다.

배송 상태(string)
배송하는 물품(map)

이 구조체를 생성하고 map형 필드값을 위해 생성자도 선언해보시기를 바랍니다.

type delivery struct {
	status string
	onedelivery map[string]int // 한번에 배송하는 물품의 뜻으로 생각
}

func newDelivery() delivery {
	d := delivery{}
	d.onedelivery = map[string]int{}
	return d
}

지금까지 구현한 코드는 바로 주문이든 장바구니 품목들의 주문이든 아이템들의 수량이 차감되고 사용자의 포인트가 차감되기만 했습니다. 이제는 여기에 추가로 배송을 시작해 배송 상태를 시간의 흐름에 따라 값을 바꿔주고, onedelivery 맵에 배송하는 물품의 이름과 수량을 추가하면 됩니다.

deliverylist := make([]delivery, 5) // 배송 중인 상품 목록

for i := 0; i < 5; i++ { // 배송 상품 객체 5개 생성
	deliverylist[i] = newDelivery()
}

for i := 0; i < 5; i++ {
	time.Sleep(time.Millisecond) //고루틴 순서대로 실행되도록 약간 딜레이
	go deliveryStatus(/*아직 미정*/)
}

각 트럭은 접수된 주문에 따라 비동기적으로 프로세스를 진행합니다. 따라서 트럭의 기능을 각각 부여하기 위해 고루틴을 5개 만듭니다. 이 고루틴이 바로 아까 말한

배송 진행
물품 싣기

기능을 하게 합니다.(deliveryStatus() 함수)

이제 주문(바로, 장바구니) 기능에 두 가지를 추가하면 됩니다.

고루틴에 배송 시작 송신(배송 진행)
주문한 상품과 수량을 onedelivery 맵에 저장(물품 싣기)

우선 주문을 하면 송신으로 배송 시작을 알리고, deliveryStatus 루틴에서 수신하면 주문접수 - 10초 대기 - 배송중 - 30초 대기 - 배송완료 - 10초 대기 - 배송 상태 초기화를 구현해봅니다. 그 후에 물론 numbuy를 1 감소시켜야합니다.

우선 배송의 시작을 알리는 채널을 생성합니다. 이 채널은 bool형으로 주문이 접수되면 true를 송신합니다. 당연히 이 채널을 사용하기 위해 이 채널을 사용하는 함수는 모두 매개변수로 추가해야합니다.

deliverystart := make(chan bool) // 주문 시작 신호 송/수신 채널
buying(items, buyer, itemchoice, &numbuy, deliverystart)
bucketBuying(items, buyer, &numbuy, deliverystart)
// buying(itm []item, byr *buyer, itmchoice int, num *int, d chan bool) 함수 바로 주문 부분
if buy == 1 { // 바로 구매
	if *num < 5 {
		itm[itmchoice-1].amount -= inputamount
		byr.point -= itm[itmchoice-1].price * inputamount	

		d <- true // 배송 시작

		*num++

		fmt.Println("상품이 주문 접수 되었습니다.")	
		break
	}
}

//func bucketBuying(itm []item, byr *buyer, num *int, d chan bool) 주문 부분
else { 
	if *num < 5 {
		for index, val := range byr.shoppingbucket {
			for i := range itm {
				if itm[i].name == index {
					itm[i].amount -= val // 수량 차감
					byr.point -= itm[i].price * val // 포인트 차감
				}
			}
		}
		d <- true // 배송 시작
		byr.shoppingbucket = map[string]int{}  // 장바구니 초기화
		fmt.Println("상품이 주문 접수 되었습니다.")
	} else {
		fmt.Println("배송 한도를 초과했습니다. 배송이 완료되면 주문하세요.")
	}		
 }

배송 상태 확인(3)


이제 deliveryStatus() 함수를 구현해보겠습니다. 이전에서 고루틴으로 이 함수를 호출해 main루틴은 진행하고 따로 실행되고 있는 함수를 만든다고 했습니다. 그리고 이 함수들은 각각의 트럭에 기능을 부여하기 때문에 5개의 고루틴에서 실행되고 각각 번호가 부여된다고 했습니다.

사진

func deliveryStatus(d chan bool, i int, deliverylist []delivery, num *int) {
	for{
		if <-d {
			deliverylist[i].status = "주문접수"
			time.Sleep(time.Second*10)

			deliverylist[i].status = "배송중"
			time.Sleep(time.Second*30)

			deliverylist[i].status = "배송완료"
			time.Sleep(time.Second*10)

			deliverylist[i].status = "" 
			*num--
		}
	}	
}

주문을 하는 시점에서 i값을 이용해 대기준인 트럭에 주문 물품을 실어야하는데 어느 몇번 트럭이 대기중인지 알 수 없습니다. 여기서 트럭 번호의 기능을 하는 것은 바로

tempdelivery := make(map[string]int) // 배달 물품 임시 저장

for i := 0; i < 5; i++ {
	time.Sleep(time.Millisecond) //고루틴 순서대로 실행되도록 약간 딜레이
	go deliveryStatus(deliverystart, i, deliverylist, &numbuy, &tempdelivery)
}

이 부분입니다. 그래서 고루틴으로 함수가 호출되면서 i값을 전달받는 것입니다. 따라서 상품을 주문하면 중간에 주문 정보를 잠깐 저장하고 대기중인 트럭이 바로 이 정보를 가져갈 수 있도록 중간 저장 변수를 만들어야합니다. 주문한 품목과 수량을 저장하는 map인 tempdelivery를 생성합니다.

그러면 주문 기능을 하는 곳에서는 해당 품목을 바로 onedelivery 맵에 저장하는 것이 아니라 우선 tempdelivery에 저장하고 채널이 수신될 때 tempdelivery값 대기하고있던 deliverylist[i].onedelivery에 저장하면 됩니다.

//func buying(itm []item, byr *buyer, itmchoice int, num *int, d chan bool, temp map[string]int) 함수 바로 주문 부분
if buy == 1 { // 바로 주문
	if *num < 5 {
		itm[itmchoice-1].amount -= inputamount
		byr.point -= itm[itmchoice-1].price * inputamount	
		temp[itm[itmchoice-1].name] = inputamount // 임시 저장

		d <- true

		*num++

		fmt.Println("상품이 주문 접수 되었습니다.")	
		break
	} else {
		fmt.Println("배송 한도를 초과했습니다. 배송이 완료되면 주문하세요.")
		break
	}		

}

//func bucketBuying(itm []item, byr *buyer, num *int, d chan bool, temp map[string]int) 함수 장바구니 물품 주문 부분
if *num < 5 {
	for index, val := range byr.shoppingbucket {
		temp[index] = val // 임시 저장
		
		for i := range itm {
			if itm[i].name == index {
				itm[i].amount -= val // 수량 차감
				byr.point -= itm[i].price * val // 포인트 차감
			}
		}
	}
	d <- true // 배송 시작

	byr.shoppingbucket = map[string]int{}  // 장바구니 초기화
	*num++

	fmt.Println("주문 접수 되었습니다.")
}

이제는 임시 저장한 것deliveryStatus() 함수에서 가져와서 deliverylist[i].onedelivery에 저장합니다. 여러 함수에서 값을 저장하고 가져오는 것이기 때문에 포인터로 변수로 가져옵니다. 그리고 값을 저장했으면 초기화해줍니다.

func deliveryStatus(d chan bool, i int, deliverylist []delivery, num *int, temp *map[string]int) {
	for{
		if <-d {
			for index, val := range *temp { 
				deliverylist[i].onedelivery[index] = val // 임시 저장한 데이터를 배송 상품에 저장함
			}

			*temp = map[string]int{} // 임시 데이터 초기화

			deliverylist[i].status = "주문접수"
			time.Sleep(time.Second*10)

			deliverylist[i].status = "배송중"
			time.Sleep(time.Second*30)

			deliverylist[i].status = "배송완료"
			time.Sleep(time.Second*10)

			deliverylist[i].status = "" 
			*num--
			deliverylist[i].onedelivery = map[string]int{} // 배송 리스트에서 물품 지우기
		}
	}	
}

이제 트럭에 상품도 실었고 배송 상태까지 변경합니다. 모든 기능이 구현됐습니다. 이제 4번 기능'배송상태 확인'만 구현하면 됩니다.
(주문 상품1 이름), (배송 수량)개 /(주문 상품2 이름), (배송 수량)개 /..... 배송상황 : (배송상황) 포맷으로 출력합니다. 그리고 만약 배송중인 것이 없으면 “배송중인 상품이 없습니다.”를 출력합니다.

이를 구현하기 위해

배송중인 상품이 있는지 없는지 확인
배송 상황 출력
else if menu == 4 { // 배송 상태 확인
	total := 0
	for i := 0; i < 5; i++ {
		total += len(deliverylist[i].onedelivery)
	}			
	if total == 0 {
		fmt.Println("배송중인 상품이 없습니다.")
	} else {
		for i := 0; i < len(deliverylist); i++ {
			if len(deliverylist[i].onedelivery) !=0 { // 배송중인 항목만 출력
				for index, val := range deliverylist[i].onedelivery{
					fmt.Printf("%s %d개/ ", index, val) 
				}
				fmt.Printf("배송상황: %s\n", deliverylist[i].status)
			}
		}
	}
	fmt.Print("엔터를 입력하면 메뉴 화면으로 돌아갑니다.")
	fmt.Scanln()
}

이렇게 하면 정말 주문한 것에 대해 배송 상황을 확인할 수 있습니다. 비동기성 프로그램을 직접 구현한 것입니다.


© 2022. All rights reserved. 신동민의 블로그