go 언어에서 여러개의 goroutine을 동시에 한단계씩 실행하기
By SeukWon Kang
2015-02-24 추가 구글+ Jae-min Park 님께서 제가 잘못알고 있던부분을 알려주셨습니다. sync 의 waitgroup은 goroutine의 종료를 wait하는 것이 아니고 Done을 wait하는 것이므로 충분히 아래의 코드를 대치하는 것이 가능한 듯합니다. ( 결국 아래 코드는 뻘짓인 셈입니다. ^^;; )
Jae-min Park, Jongmin Kim 께 감사드립니다.
RunStep 과 sync.groupwait의 차이는 실행/완료시 인자를 전달 할수 있느냐 + 각각 step을 제어 할것이냐 아니면 group으로 제어 할것이냐 의 차이가 있어 용도에 맞게 쓰면 될것 같기도 합니다.
이하 원문입니다.
여러 object가 있고 이들에게 한동작(step function)씩 실행하고 싶은 경우가 종종 발생합니다.
-
for loop를 돌면서 step function을 call한다. 문제점 - 오래 걸린다. cpu를 하나밖에 못쓴다.
-
for loop 내에서 go step() 형태로 실행한다. 문제점 - 모든 object의 step 이 완료 되었는지 알수가 없다. for loop가 종료되어 다음 문장이 실행되고 있는 중에 아직 step 함수가 실행중이어서 문제가 생길수 있다. ( 실제 경험) ( 동시에 메모리를 수정하는 버그가 생김 )
-
종료됨을 알려주는 channel을 object 갯수만큼 만들고 step function이 완료되면 channel에 쓴다. 문제점 - 매번 channel을 만들어야 한다. garbage collector 에 부하를 주게 된다.
-
각 object에 종료 channel을 미리 만들어 둔다. 문제점 - 매번 go routine을 만들어야 한다. - 이것 역시 부하를 늘이는 일. 매번 goroutine이 생성, 실행, 종료, GC 되는 것은 비효율적.
-
각 object별로 미리 goroutine을 실행해두고 step 시작 channel을 통해 시작 알림을 받는다. 완료되면 완료 channel을 통해 완료를 알려준다. 문제점 매번 코딩하기 귀찮다.
-
그래서 만든 라이브러리. ( 실제 위 단계를 모두 거쳐서 만들게 되었습니다. ;;; )
//start type RunStep struct { startStep chan interface{} resultCh chan interface{} }
func NewRunStep() *RunStep { return &RunStep{ make(chan interface{}), make(chan interface{}), } }
func (fs *RunStep) StartStepCh() chan<- interface{} { return fs.startStep }
func (fs *RunStep) ResultCh() <-chan interface{} { return fs.resultCh }
func (fs *RunStep) Run(stepfn func(d interface{}) interface{}) { for stepdata := range fs.startStep { fs.resultCh <- stepfn(stepdata) } }
func (fs *RunStep) Quit() { close(fs.startStep) select { case <-fs.resultCh: default: } }
//end
코드 자체는 무척 간단합니다. ( 애초에 간단한것만 적고 있습니다. ^^ ) 사용법은 아래와 같습니다. ( 사용 예제가 더 긴것 같지만 넘어가지요 ^^ ) 이 코드 역시 goguelike에쓰는 코드를 약간 수정한 것입니다. ( 예로 쓰기위해 )
//start
type StepRunObj struct {
*RunStep
Mode int
}
func NewStepRunObj() *StepRunObj {
aib := &StepRunObj{}
aib.RunStep = NewRunStep()
return aib
}
func (aib *StepRunObj) Step(datain interface{}) interface{} {
// do step work
time.Sleep(1 * time.Second)
return 0
}
func RunAll() {
// init objs
objs := [10]*StepRunObj{}
for i, _ := range objs {
objs[i] = NewStepRunObj()
go objs[i].Run(objs[i].Step)
}
// run step
for _, v := range objs {
v.StartStepCh() <- 0
}
// do other work
time.Sleep(3 * time.Second)
// confirm all end
for _, v := range objs {
<-v.ResultCh()
}
}
//end
ps) 종료함수를 추가 했습니다.
ps2) 구글 +의 Jongmin Kim 님께서 알려주신 sync.WaitGroup을 쓰는 것도 고민해 봤는데 waitgroup은 매번 goroutine을 실행하고 종료 한다는 점에서 4번 이슈에 대한 답은 아닌것 같습니다. 아무리 goroutine이 가볍다고 해도 미리 만들어 좋고 세웠다 실행했다는 반복하는 것이 더 가벼울것이라고 생각합니다.
ps) 2014-02-14 추가 코드들을 github에 올려 두었습니다. https://github.com/kasworld/runstep