본문 바로가기
Go

[Go] 고루틴(Goroutine)

by weero 2022. 3. 6.

[Tucker의 Go 언어 프로그래밍] 참고

 

 

고루틴

고루틴은 Go 언어에서 관리하는 경량 스레드이다. 함수나 명령을 동시할 때 사용한다. 여러 고루틴을 갖는 프로그램을 코딩하는 것을 동시성 프로그래밍이라고 한다.

  • 고루틴을 이용하면 여러 작업을 동시에 수행할 수 있다.
  • 고루틴은 Go 언어에 내장된 기능으로 외부 라이브러리에 의존하지 않고 동시성 프로그래밍을 구현할 수 있다.
  • 멀티코어 환경에서 CPU를 더 효율적으로 사용해 빠르게 작업을 완료할 수 있다.
  • 고루틴은 기존 OS 스레드에서 발생되는 컨텍스트 스위칭에 따른 성능 손실을 최소화해서 효율적으로 사용한다.
  • 고루틴 간 메모리 간섭으로 인해 발생하는 문제점에 주의해야 한다.

 

Go 언어에서는 CPU 코어마다 OS 스레드 하나만 할당해서 사용하기 때문에 컨텍스트 스위칭 비용이 발생하지 않는다.

고루틴을 추가로 생성하는 구문(모든 프로그램은 메인 루틴(main(0 함수와 함꼐함) 하나는 무조건 가지고 있음.

go 함수_호출

해당 함수를 수행하는 새로운 고루틴이 생성되고, 호출된 함수는 새로운 고루틴에서 수행된다.

 

 

서브 고루틴이 종료될 때까지 기다리기

sync 패키지의 WatiGroup 객체를 사용하면 고루틴이 종료될 때까지 대기할 수 있다.

var wg sync.WaitGroup

wg.Add(3) // 작업 개수 설정
wg.Done() // 작업이 완료될 때마다 호출
wg.Wait() // 모든 작업이 완료될 때까지 대기

====================================

package main

import (
   "sync"
   "fmt"
)

var wg sync.WaitGroup

func SumAtoB(a, b int) {
   sum := 0
   for i := 0; i <= b; i++ {
      sum += i
   }
   fmt.Printf("%d부터 %d까지 합계는 %d입니다. \n", a, b, sum)
   wg.Done()
}

func main() {
   wg.Add(10)
   for i := 0 ; i<10; i++ {
      go SumAtoB(1, 1000000000)
   }
   wg.Wait()
   fmt.Println("모든 계산이 완료됐습니다.")
}

총 10개의 고루틴을 생성한다. 4코어 CPU이기 때문에 예제를  실행하면 CPU 사용량이 100%가 된다.

 

 

동시성 프로그래밍의 주의점

동일한 메모리 자원에 여러 고루틴이 접근하면서 문제점이 발생할 수 있다.

 

 

뮤텍스를 이용한 동시성 문제 해결

뮤텍스를 이용해 자원 접근 권한을 통제하여, 한 고루틴에서 값을 변경할 때 다른 고루틴이 건들지 못하게 한다.

뮤텍스 : mutual exclusion(상호배제)의 약자이다. 뮤텍스의 Lock() 메서드를 호출해 뮤텍스를 확보하고, 이미 Lock() 메서드를 호출한 다른 고루틴이 있다면, 나중에 호출한 고루틴은 앞서 획득한 뮤텍스가 반납될 때까지 대기하게 된다. 사용중이던 뮤텍스는 Unlock() 메서드를 호출해 반납한다.

 

package main

import (
	"fmt"
	"sync"
	"time"
)

// 패키지 전역 변수 뮤텍스
var mutex sync.Mutex

type Account struct {
	Balance int
}

func DepositAndWithdraw(account *Account) {
	mutex.Lock()			// 뮤텍스 획득
	defer mutex.Unlock()	// defer를 사용한 Unlock()

	if account.Balance < 0 {
		panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance))
	}
	account.Balance += 1000
	time.Sleep(time.Millisecond)
	account.Balance -= 1000
}

func main() {
	var wg sync.WaitGroup

	account := &Account{0}
	wg.Add(10)
	for i:=0 ; i<10 ; i++ {
		go func() {
			for {
				DepositAndWithdraw(account)
			}
			wg.Done()
		}()
	}
	wg.Wait()
}

 

뮤텍스의 사용으로 생기는 문제점

  1. 동시성 프로그래밍으로 얻는 성능 향상을 얻을 수 없다.
  2. 데드락이 발생할 수 있다.

⇒ 애초에 각 고루틴이 같은 자원에 접근하지 않으면 문제가 발생하지 않는다.즉 여전히 멀티코어의 이점을 얻으면서 뮤텍스를 쓰지 않아도 되어 뮤텍스로 인한 문제도 발생하지 않는다.

각 고루틴이 서로 다른 자원에 접근하게 하는 두 가지 방법이 있다.

  • 영역을 나누는 방법 (작업 분할 방식)
  • 역할을 나누는 방법 (역할 분할 방식)