개발

[Golang] Go 언어 빠르게 훑어보기

nova_dev 2021. 10. 26. 00:00
반응형

Go 언어 빠르게 훑어보기

기본 문법

기본 구조

package main

import "fmt"

func main() {
    fmt.Println("Hello Go")
}
$ go run hello.go

선언

var foo int // 초기화 없는 선언
var foo int = 42 // 초기화 있는 선언
var foo, bar int = 42, 1302 // 한 번에 여러 var 선언 및 초기화
var foo = 42 // 생략된 type, 유추되어 들어간다.
foo := 42 // 축약형, func 본문에서만 var 키워드 생략, 암시적 type
const constant = "This is a constant" // 상수 선언

함수

// 간단한 함수
func functionName() {}

// 매개변수가 있는 함수(type은 식별자 뒤에 옴)
func functionName(param1 string, param2 int) {}

// 동일한 type의 여러 매개변수
func functionName(param1, param2 int) {}

// 반환 type 선언
func functionName() int {
    return 42
}

// 한 번에 여러 값을 반환할 수 있음
func returnMulti() (int, string) {
    return 42, "foobar"
}
var x, str = returnMulti()

// 단순히 return으로 여러 명명된 결과를 반환한다
func returnMulti2() (n int, s string) {
    n = 42
    s = "foobar"
    // n과 s가 반환됩다
    return
}
var x, str = returnMulti2()

type 종류 (Built-in)

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32 ~= a character (Unicode code point) - very Viking

float32 float64

complex64 complex128

기타 if문, for문, array, map 등은 약간의 문법만 차이가 있을 뿐 java나 다른 언어와 비슷하다고 느꼈기 때문에 생략한다.

Struct

go 언어는 클래스가 없고 struct만 있다. struct안에는 멤버 변수가 들어갈 수 있고 struct의 멤버 함수 처럼 쓸 수 있는 MethodPointer Receiver가 있다.

// 선언
type Vertex struct {
    X, Y int
}

var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // key, value로 선언
var v = []Vertex{{1,2},{5,2},{5,5}} // Vertex 배열로 선언

// member변수에 접근
v.X = 4

// 아래와 같이 struct에 함수를 선언할 수 있다.
// 메서드로 선언하기를 원하는 struct(수신 type)는 func 키워드와 메소드 이름 사이에 있다.
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 메서드를 부르는 방식
v.Abs()

// 메서드를 변경하려면 struct에 대한 포인터를 type으로 사용해야 한다. 
func (v *Vertex) add(n float64) {
    v.X += n
    v.Y += n
}

이때, struct에는 자바와 같이 상속에 대한 직접적인 키워드는 없지만, struct내에 Embedding을 이용할수는 있다.

// 서버는 Logger가 가지고 있는 모든 메소드를 노출한다.
type Server struct {
    Host string
    Port int
    *log.Logger
}

// 임베디드 유형을 초기화하는 일반적인 방법
server := &Server{"localhost", 80, log.New(...)}

// 임베디드된 구조체에 구현된 메서드는 이렇게 사용할 수 있다.
server.Log(...) // server.Logger.Log(...)를 호출하는 것과 같음.

// 임베디드 유형의 필드 이름은 type 이름이다. (이 경우 Logger)
var logger *log.Logger = server.Logger

Interface

struct가 데이터의 집합이라면 interface는 메소드의 집합이다. struct와 마찬가지로 Embedding을 이용할 수 있다. 이런 특징 때문에 Go에서는 주로 작은 인터페이스를 이용하는 것이 권장된다고 한다.

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter는 Reader와 Writer 인터페이스를 결합한 인터페이스이다.
type ReadWriter interface {
    Reader
    Writer
}

Error

go에는 Exception Handler이 없다. 대신 오류를 생성할 수 있는 함수는 tyope의 추가 반환 값만 선언하면 error가 된다.

// 오류 내장 인터페이스 type은 오류 조건을 나타내는 일반적인 인터페이스이며, 
// 오류가 없으면 nil 값으로 표현한다.
type error interface {
    Error() string
}
func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, errors.New("negative value")
    }
    return math.Sqrt(x), nil
}

func main() {
    val, err := sqrt(-1)
    if err != nil {
        // 에러 핸들링
        fmt.Println(err) // negative value
        return
    }
    // 모두 잘 동작했으면 val을 사용한다.
    fmt.Println(val)
}

Concurrency

Goroutine

고루틴은 경량 스레드이다. (OS 스레드가 아닌 Go에서 관리).
go f(a, b)실행되는 새로운 고루틴을 시작한다.

// 그냥 함수 (나중에 고루틴으로써 시작할 수 있음)
func doStuff(s string) {
}

func main() {
    // 고루틴에서 명명된 함수 사용하기
    go doStuff("foobar")

    // 고루틴에서 익명의 내부 함수 사용하기
    go func (x int) {
        // 함수의 body
    }(42)
}

Channel

채널은 고루틴 간의 Synchronous를 처리하기 위한 방법이다. 고루틴끼리 데이터를 주고받는 통로라고 볼 수 있다. 채널은 make(자료형 미리 명시) 함수를 통해 미리 생성되어야 하고, 채널 연산자 <-를 통해 데이터를 주고 받는다. 채널은 Goroutine 사이에서 데이터를 주고 받는데 사용되는데 상대편이 준비될 때까지 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이터를 동기화하는데 사용된다.

ch := make(chan int) // int type의 채널 생성
ch <- 42             // 채널 ch 에게 value를 보낸다
v := <-ch            // 채널 ch 로부터 value를 받는다

// 버퍼되지 않은 채널이 차단된다. 사용할 수 있는 값이 없으면 블록을 읽고 읽기가 있을 때까지 블록을 쓴다.

// 버퍼링된 채널을 만든다. <buffer size> 미만의 읽지 않은 값이 쓰여진 경우 버퍼링된 채널에 쓰기가 차단되지 않는다.
ch := make(chan int, 100)

close(ch) // 채널을 닫는다(발신자만 닫아야 함)

// 채널에서 읽고 닫혔는지 테스트
v, ok := <-ch

// 만약 ok가 false이면 채널이 닫힌 것

// 채널이 닫힐 때까지 읽기
for i := range ch {
    fmt.Println(i)
}

// 다중 채널 작업에서 블록 선택, 하나가 차단 해제되면 해당 사례가 실행됨
func doStuff(channelOut, channelIn chan int) {
    select {
    case channelOut <- 42:
        fmt.Println("We could write to channelOut!")
    case x := <- channelIn:
        fmt.Println("We could read from channelIn")
    case <-time.After(time.Second * 1):
        fmt.Println("timeout")
    }
}

참고자료

반응형

'개발' 카테고리의 다른 글

[Redis] Redis 설치(docker)와 redis-cli 사용법  (0) 2022.03.29
[Spring Cloud] Hystrix 파헤치기  (0) 2021.11.07
gradle  (0) 2021.08.16
WSL(Windows Subsystem for Linux)이란?  (0) 2021.05.23
gRPC란?  (0) 2021.03.22