Golang - 채널에 따른 다중 작업 처리
Go 언어로 프로그램을 하다 보면, 자칫 goroutine을 남발해 어느 순간이 되면 프로그램의 실행 경로가 어떻게 되는지 감이 안 오는 경우가 있습니다. 물론, 비동기 처리를 훌륭하게 그려낼 수 있는 뇌구조를 가진 개발자라면 상관없겠지만, 저처럼 ^^ 일반인은 역시 동기 처리가 편할 수밖에 없습니다.
그래서 최대한 실행 경로를 구조적으로 유지하는 것이 중요한데요, 가령 channel에 따른 실행 처리를 할 때 각각의 경우 별로 goroutine을 사용할 수도 있겠지만,
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
go WorkPer1Sec()
go WorkPer5Sec()
// 프로세스 종료를 막기 위해.
bufio.NewReader(os.Stdin).ReadLine()
}
func WorkPer5Sec() {
// 5초마다 수행할 작업
for {
<-time.After(5 * time.Second)
fmt.Println("5sec elapsed")
}
}
func WorkPer1Sec() {
// 1초마다 수행할 작업
for {
<-time.After(1 * time.Second)
fmt.Println("1sec elapsed")
}
}
select를 이용해 한데 모으는 것도 가능합니다.
func main() {
go WorkPer()
bufio.NewReader(os.Stdin).ReadLine()
}
func WorkPer() {
time5 := time.Tick(5 * time.Second)
time1 := time.Tick(1 * time.Second)
for {
select {
case <-time5:
fmt.Print("5, ")
case <-time1:
fmt.Print("1, ")
}
}
}
/* 출력 결과
1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1,
*/
대신 이런 경우에는 채널이 하나의 goroutine 내에서 처리되기 때문에 동일한 CPU 스레드에서 동작한다는 특징이 있습니다. 이로 인해 분리된 채널에서의 공유 변수에 대한 동기화는 필요 없지만 스레드 점유에 따른 간섭은 발생할 수 있습니다. 가령, 다음과 같이 5초 채널에서 (I/O 발생이 아닌) CPU 소비가 필요한 동작을 하게 되면,
func WorkPer() {
time5 := time.Tick(5 * time.Second)
time1 := time.Tick(1 * time.Second)
for {
select {
case <-time5:
fmt.Print("5, ")
now := time.Now()
for {
var elapsed = time.Since(now)
if elapsed.Seconds() > 5 { // 일부러 5초가 걸리는 CPU 작업을 수행
break
}
}
case <-time1:
fmt.Print("1, ")
}
}
}
화면에는 '1' 채널의 작업에 영향을 줘 다음과 같은 식의 예측할 수 없는 출력이 발생합니다.
...[생략]..., 5, 1, 1, 5, 1, 1, 5, 5, 1, 1, 5, 5, 1, 1, 5
만약 저런 상황이라면, 각각의 channel 작업을 분리해 goroutine으로 실행하시면 됩니다.
func WorkPer() {
time5 := time.Tick(5 * time.Second)
time1 := time.Tick(1 * time.Second)
for {
select {
case <-time5:
go Work5()
case <-time1:
go Work1()
}
}
}
func Work1() {
fmt.Print("1, ")
}
func Work5() {
fmt.Print("5, ")
now := time.Now()
for {
var elapsed = time.Since(now)
if elapsed.Seconds() > 5 {
break
}
}
}
/* 출력 결과
1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1,
*/
어찌 보면 처음부터 goroutine으로 작업한 것과 별반 다를 바가 없지만, 뭔가 비동기적으로 발생하는 이벤트를 한 곳에서 확인하는 것이 관리적인 차원에서 더 나을 수 있습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]