프로그램에 있는 다른 고루틴과 관련하여 독립적으로 동시에 실행되는 함수입니다.
즉, Go 언어로 동시에 실행되는 모든 활동을 고루틴이라고 합니다.
Goroutine은 경량 스레드와 유사한 개념으로, Go 런타임 스케줄러에 의해 관리됩니다.
하지만, thead(스레드)는 OS 스레드 스케줄러에 의존하여 관리합니다.
메모리 사용량 차이
생성 속도
동시성
OS에서 여러 프로그래밍이 동작할 때 동시성 혹은 병렬성으로 동작합니다. 흔히 동시성이라고 하면 병렬성과 비슷한 개념이라고 생각할 수 있지만 이는 명확히 다른 개념입니다.
동시성은 싱글 코어에서 멀티 스레드를 동작시키는 논리적인 개념으로 한번에 여러개가 동시에 실행되는 것 처럼 보이게 됩니다. 하지만 병렬성은 물리적으로 동시에 여러작업을 처리할 수 있기 때문에 멀티 코어에서 멀티 스레드를 동작시키는 방식입니다.
블로킹
통신
Go 런타임은 프로그램이 실행되는 내내 고루틴을 관리합니다. Goruntime에 포함되어 있는 Go 런타임 스케줄러가 실행하는 쓰레드 스케줄링에 의해서 실행됩니다.
모든 고루틴을 다중화된 스레드들에 할당하고 모니터링하며, 특정 고루틴이 블록되면 다른 고루틴이 실행될 수 있도록 교체하는 일을 반복합니다. 이 말은 고루틴이 블록 되더라도 다중화된 스레드는 블록 되지 않는다는 것을 의미합니다. 모든 것은 Go 런타임이 알아서 처리해 줍니다.
G
는 LRQ에서 대기하고 있습니다.M
은 P
의 LRQ로부터 G
를 할당받아 실행합니다.P
의 포인터를 가지고 있습니다.G
를 M
에 할당합니다.LRQ
GRQ
메서드 앞에 go 라는 키워드만 붙여주면 됩니다.
func main() {
go doSomething()
doAnotherThing()
}
이처럼 go
라는 키워드를 붙혀주면 알아서 고루틴으로 동작하게 됩니다.
위처럼 작성한 코드는 아래와 같이 작동하게 됩니다.
func main() {
runtime.newProc(...)
doAnotherThing()
}
아래는 익명함수(함수명을 갖지 않는 함수)를 활용한 예시입니다.
func main() {
for i := 0; i < 3; i++ {
go func(n int) {
f.Println("goroutine : ", n)
oneTime.Do(Hello)
}(i)
}
}
아래와 같은 코드가 있다고 가정하자.
package main
import (
"fmt"
"time"
)
// 공유 자원
var count int
// 0 ~ 49까지 총 50번 반복
// 각 반복에서 count를 1씩 증가시키고 값을 출력
func sub() {
for i := 0; i < 50; i++ {
count++
time.Sleep(time.Second / 10)
fmt.Printf("sub: %d\n", count)
}
}
func main() {
go sub() // sub() 함수를 고루틴으로 시작
for i := 0; i < 50; i++ {
count++
time.Sleep(time.Second / 10)
fmt.Printf("main: %d\n", count)
}
}
… 이하 생략
위처럼 동시에 작업이 일어남을 알 수가 있다.
하지만, 여기서 count
라는 공유자원을 sub
함수와 main
함수 두 곳에서 동시에 접근하고 변경합니다. 즉, count
값의 증가와 출력 순서가 예측 불가능해집니다. 보통 뮤텍스를 통해 해결하지만 Go에서는 채널
이라는 개념을 활용해서 해결한다.
Go 언어에서 채널이란 고루틴끼리 데이터를 주고받는 통로(파이프)의 역할을 수행합니다.
버퍼 없는 채널 (Unbuffered Channel)
package main
func main() {
// 정수형 채널을 생성한다
ch := make(chan int)
go func() {
ch <- 123 // 채널에 123을 보낸다
}()
var i int
i = <- ch // 채널로부터 123을 받는다
println(i)
}
버퍼 있는 채널 (Buffered Channel)
make
함수를 사용하여 버퍼 크기를 지정합니다. 예: ch := make(chan int, 100)
package main
import (
"fmt"
"time"
)
func main() {
// 버퍼채널은 두번째 매개변수로 버퍼의 갯수를 지정하며 생성합니다.
ch := make(chan string, 1)
go sendToChannel(ch)
<-time.After(time.Second)
fmt.Println("main routine finish")
}
func sendToChannel(ch chan string) {
// 버퍼채널이 아니라면 수신대기가 없다면 블락
// 버퍼채널은 수신대기가 없어도 블락되지않고 로직은 진행
ch <- "hello"
fmt.Println("send finish")
}
버퍼 있는 채널의 사용 상황
주의할 점