Backend with Golang

[Concurrency-6] Or-done channel

아직개구리 2023. 9. 14. 21:55

값이 들어오는 channel이 있을 때 단순하게 for range 문을 사용해서 그 채널에서 값을 받아오게 되면, done채널을 사용해서 취소하기가 쉽지 않다. 그렇기 때문에 아래와 같이 for 와 select 문을 함께 사용한다. 기존에 4번 글(https://sea-green.tistory.com/25 )에서 다뤘던 부분 중 Done channel이 닫히기 전까지 무한루프를 돌며 작업을 수행하는 코드이다. done 채널을 포함한 select 문을 사용하는 것이다. 

//1. 
for { 
	select {
		case <-done: 
        	return
		default: 
    }
    // Do non-preemptable work
} 

//2. 
for {
	select {
		case <-done: 
        	return
		default:
			// Do non-preemptable work
	} 
}

 

이런 코드 형태를 따라 이런 식으로 할 수 있다. 

for {
	select{
    case v := <-valStream: 
    	//do something with v
    case <-done:
    	return 
    }
}

 

이런 것이 반복되게 되면, verbosity가 커지기 때문에 이를 encapsulate 할 수 있는 방법을 소개한다. 

요구사항이 아래와 같을 때 

  • myChan에서 들어오는 값들을 처리해야하고,
  • 처리를 하다가 done channel을 통해서 취소할 수 있어야 하며,
  • myChan이 close되었을 때 작업이 끝나야한다. 

orDone 함수를 사용해서 encapsulate할 수 있다. 

orDone := func(done, c <-chan interface{}) <-chan interface{} {
	valStream:= make(chan interface{})
    go func() {
    	defer close(valStream)
        for {
        	select {
            	case <- done: 
                	return 
                case v, ok := <-c: 
                	if ok == false {
                    	return 
                    }
                    select {
                    case valStream <- v:
                    case <-done: 
                    }
            
            }
        }
    }()
    return valStream
}

for val := range orDone(done, myChan) {
	// Do something with val 
}

아래 for loop과 같이 간단하게 쓸 수 있다. 

 

두번째 select 문이 있는 이유는 무엇일까? valStream <- v만 하지 않고, select문을 추가한 이유는? 

만약에 done 채널로 인해서 canceled되고 싶은데, 만약에  valStream이 읽히지 않아서 block 된다면, cancel되지 않을 수 있다. block 되지 않고 취소가 되도록하기 위해 안에 select 문이 추가가 되는 것이다. 만약 없이 valStream <- v 만 있었다면, block되어 done이 close되었어도 취소되지 않았을 것이기 때문에 무조건 이 부분이 있어야 한다.