Go routine
Go에서 java의 Thread 같은 비동기적으로(asynchornously) 함수를 실행하는 방법이다. goroutine은 OS 쓰레드보다 훨씬 가볍게 처리를 구현하기위해 만든것이라 한다. 그래서 OS 쓰레드 하나에서 여러개의 goroutine을 실행하기도 한다고 합니다. 결론은 훨씬 적은 자원의 쓰레드를 사용한다는것 같다. 메모리 또한 적은 자원을 사용!
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import (
"fmt"
"time"
)
func printTest(s string){
for i := 0 ; i < 10; i++{
fmt.Print( s )
}
}
func main(){
printTest( "hello")
go printTest("wow!")
go printTest("wonderful!")
go printTest("golang!")
// 따로 구현하지 않으면 main 함수는 goroutine들을 실행하고 기다려 주지 않는데 그래서 goroutine이 중간에 꺼져버리게 된다. 그래서 임의로 기다려 주는코드를 넣었다.
time.Sleep(time.Second * 3)
}
익명함수 및 sync 사용
아래 처럼 사용하면 익명함수로 goroutine을 사용 할 수 있고 sync 패키지를 사용하여 goroutine이 종료되기를 기다리는 동작을 하게 구현해놓았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"sync"
)
func main() {
// WaitGroup 생성. 2개의 Go루틴을 기다림.
var wait sync.WaitGroup
wait.Add(2)
// 익명함수를 사용한 goroutine
go func() {
defer wait.Done() //끝나면 .Done() 호출
fmt.Println("Hello")
}()
// 익명함수에 파라미터 전달
go func(msg string) {
defer wait.Done() //끝나면 .Done() 호출
fmt.Println(msg)
}("Hi")
wait.Wait() //Go루틴 모두 끝날 때까지 대기
}
다중처리 CPU 관련해서도 글이 있었는데 잘 이해되지 않고 애매한 부분이 있어서 나중에 좀 더 공부해보기로 했다.
Channel
Go Channel은 데이타를 주고 받는 통로?라고 얘기하고 있다. goroutine에서 흔히 사용되는데 데이타를 주고받는 상황에서 상대편이 준비될 떄까지 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이타를 동기화로 사용된다.
channel 사용법
동작 | 방법 |
---|---|
channel생성 | make([데이타 타입]) |
데이타 보내기 | channel명 <- 데이타 |
데이타 받기 | <- channel명 |
example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
func main() {
// 정수형 채널을 생성한다
ch := make(chan int)
go func() {
ch <- 123 //채널에 123을 보낸다
}()
var i int
i = <- ch // 채널로부터 123을 받는다. 데이타가 올때까지 대기
println(i)
// <- ch ; 이와 같이 쓰면 데이타를 따로 처리는 하지 않고 기다리기만하는 것도 가능
}
goroutine을 기다리는 부분이 ch에서 수신을 대기하므로 이전과 같이 따로 대기하는 부분을 만들지 않아도 된다.
Channel Buffering
channel은 2가지의 형식?이 있다. Unbuffered, Buffered 위에 예시는 Unbuffered channel로 수신자가 데이타를 받을 때까지 송신자가 데이타를 받을 때까지 송신자가 데이타를 보내는 체널에 묶에 여있게 된다. 반대로 Unbuffered는 수신자가 준비가 되어있지 않아도 지정된 버퍼만큼 데이타를 보내고 계속 다른 일을 수행할 수 있게 된다.
이떄 buffer가 넘치게 되면 오류가 발생한다.
Unbuffered에서 미수신시 에러
1
2
3
4
5
6
7
8
9
10
11
12
// error example
package main
import "fmt"
func main(){
c := make(chan int) // Unbuffered channel 생성
c <- 12 // 수신자가 없어서 무한이 대기한다. - Dead Lock
fmt.Println(<- c) // 코맨드로 같은 함수에서 호출해도 별도의 GoRoutine이 아니기 때문에 오류가 난다.
}
Buffered 사용
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main(){
c := make(chan int , 1)
ch <- 35 //수신자가 없어도 보낼 수 있어서 다름 코드로 넘어간다.
// ch <- 56 // 버퍼 크기 이상 송신시 에러 발생
fmt.Println( <- ch )
}
Channel Parameter
channel을 함수의 파라미터로 전달할때, 특별히 해당 channel로 송신 또는 수신만 할 것인지 지정할 수 있다.
송신 | chan<- |
수신 | <-chan |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"
func main(){
ch := make(chan string, 1)
sendChan(ch)
receiveChba(ch)
}
func sendChan(ch chan<- string){
ch <- "안녕하세요"
// 만약 <- ch 같이 수신을 한다면 에러 발생
}
func receiveChan(ch <-chan string){
receiveData := <- ch
fmt.Println(receiveData)
}
Channel Close
channel은 데이타를 송신한 후 close() 함수를 사용하여 channel을 닫을 수 있다. 태널을 닫게 되면, 해당 channel은 송신을 할 수 없다. 그러나 수신은 계속 가능하다. 수신시에 2개의 리턴값을 받게 되는데 두번째 값이 수신이 확인 값이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"
func main(){
ch := make(chan int, 2)
ch <- 45
ch <- 32
close(ch)
fmt.Println(<- ch)
fmt.Println(<- ch)
if _, ok := <- ch; !ok { // ok는 bool 타입
fmt.Println("데이터 없음")
}
}
Channel range 문
위 문법으로는 수신 한번에 한하여 channel이 닫혀있는지 확인하지만 range문을 사용하면 for문에서 위의 코드를 사용하지 않고 간결하게 닫히기 전까지 channel에 데이타를 수신할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
func main(){
ch := make( chan int , 2)
ch <- 1
ch <- 2
close(ch)
for data := range ch {
println(i)
}
// 위와 같은 코드는 아래와 같은 코드와 같은 동작을 하게된다.
// for {
// if data, ok := <- ch; ok {
// println(data)
// }else{
// break
// }
// }
}
Channel select문
select문은 여러개의 channel을 대기하는 기능을 한다. case에 channel들이 준비?가 되지 않으면 계속 대기하고 가장 먼저 도착한 channel의 case를 실행하게된다. default문이 정의 되어있다면 대기하지 않고 default에 정의된 코드를 실행한다. 만약 복수의 channel에 동시에 데이타가 온다면 랜덤하게 선택해서 처리하게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import "time"
func main() {
done1 := make(chan bool)
done2 := make(chan bool)
go run1(done1)
go run2(done2)
EXIT:
for {
select {
case <-done1:
println("run1 완료")
case <-done2:
println("run2 완료")
break EXIT
}
}
}
func run1(done chan bool) {
time.Sleep(1 * time.Second)
done <- true
}
func run2(done chan bool) {
time.Sleep(2 * time.Second)
done <- true
}
코드 출처 및 참조는 예제로 배우는 Go 프로그래밍에서 하였습니다.