Category Archives: GoLang

[GoLang] Go언어로 기초적인 webserver 만들어보기

8080 포트로 부터 해당 요청을 받아서 처리하는 기초적인 웹 서버다.

보통 Golang webframework는 gorilla를 많이 쓰는 것 같다.

package main

import (
  "log"
  "net/http"
)

const (
  PORT = ":8080"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
    w.Write([]byte(`
      <html>
        <head><title>Hello</title></head>
        <body>Hello, go web server</body>
      </html>
    `))
  })
  if err := http.ListenAndServe(PORT, nil); err != nil {
    log.Fatal("error: ", err)
  }
}

 

[Go] GoRoutine (고루틴) 을 알아보자 (1) – 고루틴이란?

GoLang에서 중요한 기능이 여러가지 있겠지만, 그 중에서 가장 중요하고 핵심적이고 재미있는 기능을 꼽는 다면 난 주저 없이 GoRoutine을 뽑을 것이다.

고루틴은 다른 함수를 동시에 실행할 수 있는 GoLang만의 특별한 함수다.

프로그램이 처리해야할 작업이 여러가지가 있는데, 이를 순서대로 처리하는 것이 아니라 이러한 작업을 동시에 처리하는 것이다.

물론 다른 언어에서 이런 기능을 제공하지 않는 것은 아니지만, GoLang은 이런 동시성 프로그래밍을 간단한 문법으로, 그리고 적은 리소스로 많은 양을 빠르게 실행할 수 있다.

아래의 예제를 살펴보자.

package main

import (
  "fmt"
  "time"
)

func goRoutine1(n string){
  for i := 0; i < 5; i++ {
    fmt.Println(n)
    time.Sleep(100 * time.Millisecond)
  }
}
func main() {
  go goRoutine1("wow!")
  goRoutine1("effort!")
}

일단 고루틴으로 함수를 실행하는 방법은 함수 호출구문 앞에 go를 붙이는 것이다. 간단하당.

일반적인 방법으로 실행했다면, wow ~~~ effort ~~~ 로 나왔겠지만 고루틴으로 실행했기 때문에

$ go run GoRoutine2.go
effort!
wow!
effort!
wow!
effort!
wow!
effort!
wow!
wow!
effort!

 

effort를 출력하는 와중에 wow도 출력하는 것을 알 수 있다.

만약에 위에서 time.Sleep을 하지 않았다면 effort 만 출력하고 끝난다.

main함수가 실행되면서 동시에 GoRoutine함수도 실행이 되는데, main 함수 실행이 끝나버리면 GoRoutine이 동시에 실행되고 있건 말건 간에 main 함수는 끝이 나기 때문이다. 즉, effort 가 goroutine에게 제어권을 넘겨주지 않고 함수가 종료한 것으로 판단해버리고 그냥 끝난 것이다.

그렇다면 모든 goroutine이 다 실행되고 끝나려면 어떻게 해야할까?

그걸 구현하기 위해선 우선 채널을 알아야한다.

 

[Go] 구조체와 임베딩 (Structure, Embedding)

Golang에는 클래스가 없다.

다만 구조체가 있을 뿐이다.

 

다음은 구조체의 예제다.

package main

import "fmt"

type work struct {
  mission string
  time int
  boss string
  salary int
}

func main() {
  programming := work{"잡일", 5, "김악덕", 100}
  fmt.Println(programming)
  fmt.Println(programming.time)
  programming.time = 100
  fmt.Println(programming.time)
}
$ go run GoStructure.go
{잡일 5 김악덕 100}
5
100

위에 struct 를 mission, boss String 이런식으로도 쓸 수 있다.

메소드는 어떻게 구현할 수 있을까?

func (total *work) getTotal() int {
  return total.time * total.salary
}
func main() {
  fmt.Println(programming.getTotal())
}

이런식으로 할 수 있다. 특별한 건 여기서, work 는 구조체를 total이라는 변수명을 사용해서 (리시버 변수) 접근하고 있고, work에 *을 붙였다는 것다.

*은 모두가 예상한 바로 그것이다. 포인터.

*이 지정되지 않으면(주소값을 할당해주지 않으면) 저 메소드는 구조체 내부의 값에 영향을 미치지 않게 된다.

 

암튼 쓰는건 여타 언어와 크게 다를 바는 없다.

 

다만 클래스가 없기 때문에 상속이라는 개념도 없다.

그렇다면 GoLang에서 상속을 흉내내려면 어떻게 해야할까?

 

바로 그것은 임베딩이다.

package main

import "fmt"

type work struct {
  mission string
  time    int
  boss    string
  salary  int
}

func (total *work) getTotal() int {
  return total.time * total.salary
}

func (total *hardWork) getTotal() int {
 return total.w.time * total.w.salary - 100000
}
type hardWork struct {
  w      work
  level  int
  yaguen bool
}

func main() {
  var spring hardWork
  spring.w.boss = "김악마"
  spring.w.time = 100
  spring.w.mission = "의미없이 스프링 문서를 보고 있을 것"
  spring.w.salary = -100
  spring.level = 10000
  spring.yaguen = true
  
  fmt.Println(spring)
  fmt.Println(spring.w.getTotal())
  fmt.Println(spring.getTotal())

}
$ go run GoStructure.go
{{의미없이 스프링 문서를 보고 있을 것 100 김악마 -100} 10000 true}
-10000
-110000

대충 보면 느낌이 올 것이다. hardwork는 work를 임베딩 하고 있으며, 변수명을 w 로 work를 가지고 있다. 변수명은 생략가능하다.

그리고 명칭이 같은 메소드를 오버라이딩하고 있는데, 이 두개에 대한 동작이 다른 것을 볼 수 있다.

 

그렇다.

 

그러하다.

[Go] Panic, Recover

앞선 포스트로 지연함수를 만들어 보았는데, 마치 finally처럼 동작하는 것을 볼 수 있었다.

그렇다면 try & catch는 어떻게 구현할 수 있을까?

GoLang에서는 Panic 과 Recover라는 것을 지원한다.

  1. Panic
    패닉은 exception이라고 보면 된다. 이름 참 잘지었다.

    package main
    
    import "fmt"
    
    func main() {
      temp_list := [...]int{1, 2, 3}
      for i := 0; i < 5; i++ {
        fmt.Println(temp_list[i])
      }
    }
    
    $ go run PanicRecover.go
    1
    2
    3
    panic: runtime error: index out of range
    
    goroutine 1 [running]:
    panic(0x4f0360, 0xc082002040)
            C:/Go/src/runtime/panic.go:481 +0x3f4
    main.main()
            D:/Study/Go_Study/GoBasicStudy/PanicRecover.go:8 +0x14a
    exit status 2

    Panic이 발생했다. 사용자가 맘대로 패닉을 띄울 수도 있다.

    package main
    
    import "fmt"
    
    func main() {
      defer fmt.Println("hello")
      panic("이렇게 내 마음대로 메시지를 만들어서 panic을 발생시킬 수 있습니다.")
      fmt.Println("what the hell?")
    }
    
    $ go run PanicRecover.go
    hello
    panic: 이렇게 내 마음대로 메시지를 만들어서 panic을 발생시킬 수 있습니다.
    
    goroutine 1 [running]:
    panic(0x4cbe20, 0xc08203a200)
            C:/Go/src/runtime/panic.go:481 +0x3f4
    main.main()
            D:/Study/Go_Study/GoBasicStudy/PanicRecover.go:14 +0x150
    exit status 2
  2. Recover
    Recover는 Catch라 할수 있다. 다만 주의할 점은 defer 안에서 recover를 해야 한다는 것.

    package main
    
    import "fmt"
    
    func main() {
      defer func() {
        panicMsg := recover()
        fmt.Print(panicMsg)
        fmt.Println("를 Recover로 붙잡았습니다.")
      }()
    
      panic("Custom Error!!")
    }
    
    $ go run Recover.go
    Custom Error!!를 Recover로 붙잡았습니다.

    panic으로 custom error라는 메시지가 뜨는 게 아니라 지연함수 안에서 recover가 해당 에러메시지를 처리하는 것을 볼 수 있다.

 

상황에 맞게 적절하게 defer, panic, recover를 사용하자.

퇴근하고 싶다.

[Go] 지연함수, defer

지연함수란 함수가 끝나기 직전에 호출되는 함수를 의미한다.

defer 함수 이런식으로 선언하면 된다.

아래의 예제를 확인해보자.

package main

import "fmt"

func main() {
  defer func() {
    fmt.Println("Hello?")
  } ()

  for i := 0; i < 5; i++ {
    defer fmt.Println(i)
  }

  fmt.Println("wow.")
}

위의 코드를 실행하면 어떻게 작동할까?

$ go run defer.go
wow.
4
3
2
1
0
Hello?

LIFO형식으로 작동하는 것을 볼 수 있다. 즉 맨 마지막에 세팅한 지연함수부터 실행된다.

이런 지연함수는 언제 사용하면 좋을까?

아마도 dbconnection이나 fileopen을 하고 꼭 close를 해야할 때 유용하게 쓸 수 있을 것이다.

프로그램 분기가 어떻게 되든 – 마지막에는 반드시 close를 호출 할 것이기 때문이다.