golang
- golang은 stack 자료형이 없다.
- arraylist가 제공되지 않는다. 배열로 append 한다.
- append는 있지만 delete는 제공되지 않는다.
- rune 타입이라는게 있다. int 와 같은 형태지만 int와는 다르다.
- map 이 서로 같은지 비교하려면 reflect.DeepEqual(a,b)를 해야한다.
- golang은 관례상 error 값이 함수 리턴의 마지막 값이다.
func main() (data, error) {
}
- golang은 가변길이 파라미터를 지원한다.
package main
import "fmt"
func varchar(txt... string) {
fmt.Println(txt)
}
func main() {
varchar("123", "234", "234")
}
-
golang의 메소드를 대문자로 선언하는 이유
- golang은 따로 private, public이 없다.
- 메소드 이름을 대문자로 선언하면 다른 모듈에서 보인다.
- 메소드 이름을 소문자로 선언하면 다른 모듈에서 안 보인다.
-
golang에서 비동기 setTimeout을 사용하는 방법
time.AfterFunc(5*time.Second, func(){
// 콜백으로 실행될 내용
})
fmt.Println("먼저 실행")
// 5초후 실행된다.
- golang에서는 enum이 따로 없다. 상수를 정의해서 사용한다.
type status int
const (
UNKNOWN status = iota
TODO
DONE
)
func main() {
fmt.Println(UNKNOWN) // 0 출력
fmt.Println(TODO) // 1 출력
fmt.Println(DONE) // 2 출력
}
-
golang은 테스트에서 assertEqual이 없다. 직접 만들어서 써야한다.
-
golang은 값에 의한 호출만 지원한다.
- call by value
package main
import "fmt"
type Str struct {
Name string
Age int
}
func funcA(str *Str) {
str.Name = "ChangeName"
fmt.Println(*str)
}
func main() {
str := Str{ // 구조체를 만들고
Name: "hello",
Age: 10,
}
fmt.Println(str)
funcA(&str) // 주소를 함수에 전달하는 방식
fmt.Println(str)
}
- golang의 에러 핸들링
- if 문에서 err := 로 대입한다. 그리고 err가 nil이 아니면 처리한다.
package main
import (
"errors"
"fmt"
)
func funcA() error {
return errors.New("hello error")
}
func main() {
if err := funcA(); err != nil {
fmt.Println("error")
fmt.Println(err)
}
}
- json data의 처리
보통의 json 문자열의 경우에는 json.Unmarshal해서 처리한다.
func main() {
dd := `{
"name": "daf",
"age": 3
}`
fmt.Println(reflect.TypeOf(dd))
var obj interface{}
err := json.Unmarshal([]byte(dd), &obj)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("--------\n", reflect.TypeOf(obj))
}
}
Unmarshal 하면 map[string] interface{} 형태의 값을 얻을 수 있다.
- 그럼 이번엔 코드 데이터를 json 문자열로 변환한다.
type Serial struct {
Name string
Age int
}
func main() {
serial := Serial{
Name: "hello",
Age: 10,
}
byteArr, err := json.Marshal(serial)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(byteArr))
}
Serial이라는 구조체를 선언했다.
json.Marshal()을 하면 byte 배열이 나온다.
바이트 배열을 string() 해주면 json 문자열로 변경된다.
- map[string]string도 Json 형태로 바꿀 수 있다.
package main
import (
"encoding/json"
"fmt"
)
func main() {
var mapTest map[string]string
mapTest = make(map[string]string)
mapTest["hello"] = "hihi"
jsonMap, err := json.Marshal(mapTest)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(jsonMap))
}
map은 make(map[string]string)처럼 make를 해서 인스턴스를 생성해야한다.
- golang에서 구조체는 필드들의 집합이다.
- golang에서 인터페이스는 함수들의 집합체이다.
type Person struct {
Name string
Age int
}
type PersonActivity interface {
Call()
Work(cnt int)
}
func main() {
}
- golang에서 구조체와 인터페이스를 같이 쓰는 방법
package main
import "fmt"
type Person struct { // Person 구조체를 정의한다.
// 구조체는 클래스인데 메소드가 없다고 생각하면 된다.
Age int
Name string
}
type IPerson interface { // 인터페이스를 정의한다.
// 인터페이스는 필드 없이 메소드만 있는 클래스라고 생각할 수 있다.
IsWork() bool
GetAge() int
GetName() string
}
func (p Person) IsWork() bool {
//함수 앞쪽에 구조체를 정의한다. IsWork를 구현했으므로 IPerson 인터페이스도 구현했다.
// 이런식으로 해당 구조체의 함수를 정의하고 쓸 수 있다.
fmt.Println(p)
return false
}
func main() {
person := Person{ // 클래스처럼 객체를 만든다.
Name: "hello",
Age: 10,
}
person.IsWork() //이렇게 전달하면 IsWork에서는 p가 생성한 객체로 전달된다.
}
- golang defer 함수
- golang의 defer 함수는 finally 같은 역할을 한다.
- socket, file close에서 사용할 수 있다.
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("1.txt")
defer f.Close()
if err != nil {
fmt.Println(err)
}
fmt.Println(f)
// defer를 위에 썼지만 실제로 파일읽기가 close 되는 것은 main 함수가 종료되기 직전이다.
}
- golang panic
- panic은 현재 함수를 멈춘다.
- 현재 함수를 멈추고 defer를 모두 실행하고 리턴한다.
- recover 함수를 사용하면 panic 상태를 다시 정상으로 만들 수 있다.
package main
import (
"fmt"
"os"
)
func main() {
// 잘못된 파일명을 넣음
openFile("Invalid.txt") // 1
// recover에 의해
// 이 문장 실행됨
println("Done") // 7
}
func openFile(fn string) {
// defer 함수. panic 호출시 실행됨
defer func() { // 4 defer 호출
if r := recover(); r != nil { // recover가 실행된다.
fmt.Println("OPEN ERROR", r) // 5
}
}()
f, err := os.Open(fn) // 2
if err != nil {
panic(err) // 3 패닉 발생. 여기서 멈추고 defer 를 호출한다.
}
defer f.Close() // 6
}
-
고루틴, 채널
-
고루틴의 목적
- OS 스레드보다 가볍게 비동기 처리를 하기 위해서 만들었다.
- OS 스레드와 1:1 대응되지 않는다.
- OS 스레드가 1MB의 스택을 갖는다. 고루틴은 몇 KB의 스택을 갖는다.(필요시 동적으로 증가한다.)
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 10; i++ {
fmt.Println(s, "***", i)
}
}
func main() {
// 함수를 동기적으로 실행
say("Sync") // 함수가 10번 실행된다.
// 함수를 비동기적으로 실행
go say("Async1") // 자바의 스레드처럼 순서를 보장하지 않는다.
go say("Async2") // 1,2,3 이 무작위로 번갈아가면서 나온다.
go say("Async3")
// 3초 대기
time.Sleep(time.Second * 3)
}
보통 이렇게 무작위로 실행되는 비동기 작업은 흐름을 제어할 필요가 있다.
wait 를 사용해서 고루틴을 기다릴 수 있다.
package main
import (
"fmt"
"sync"
)
func main() {
var wait sync.WaitGroup
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("1")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("2")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("3")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("4")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("5")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("6")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("7")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("8")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("9")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("10")
}()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
fmt.Println("adsf")
}
wait.Wait() 부분을 주석처리했다.
이렇게 코드를 작성하면 아래처럼 실행 순서가 섞인다.
4
6
3
2
1
5
7
8
9
adsf
10
실행 순서를 유지하고 싶다면 wait.Wait()를 사용해야한다.
package main
import (
"fmt"
"sync"
)
func main() {
var wait sync.WaitGroup
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("1")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("2")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("3")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("4")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("5")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("6")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("7")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("8")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("9")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("9")
}()
wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
fmt.Println("adsf")
}
wait.Add()
go func()
wait.Wait()
이 3가지는 세트로 같이 사용해야한다.
wait에 추가되지 않으면 몇개의 go func 를 기다려야 하는지 모르기 때문에 기다리지 않는다.
wait.Wait()가 없어도 기다리지 않는다.
package main
import (
"fmt"
"sync"
)
func main() {
var wait sync.WaitGroup
wait.Add(1) // 몇개의 go routine을 기다릴 것인지 지정
go func() {
defer wait.Done()
fmt.Println("1")
}()
wait.Wait()
// wait.Wait() // wait가 있기 때문에 asdf 보다 hello hi가 항상 먼저 나온다.
fmt.Println("adsf")
}
channel을 만들고 데이터 보내기
package main
import (
"encoding/json"
)
func main() {
ch := make(chan map[string]string)
go func() {
m := make(map[string]string)
m["a"] = "df"
ch <- m
}()
i := <-ch
jsonI, err := json.Marshal(i)
if err != nil {
println(err)
}
println(string(jsonI))
}
channel을 사용하면 wait를 쓰지 않고 wait를 할 수 있다.
channel을 써서 여러개의 데이터를 처리하려면 이렇게 한다.
package main
func main() {
c := make(chan int)
go func() {
c <- 1
c <- 2
c <- 3
c <- 4
close(c) // 안 쓰면
//fatal error: all goroutines are asleep - deadlock! 에러가 발생한다.
}()
for num := range c {
println(num)
}
}
채널이 열렸는지 닫혔는지도 알 수 있다.
package main
func main() {
chValue := make(chan int)
go func() {
chValue <- 1
close(chValue)
}()
num, ok := <-chValue
println(num) // 채널로 받아온 값 출력
println(ok) // 채널이 닫혔는지 여부
}
- 채널의 버퍼 크기를 직접 정할 수도 있다.
- 하지만 권하지 않는다.
- 제한된 버퍼가 가득차면 고루틴이 채널에 보내는 곳에서 멈춘다.
- 버퍼 없는 채널로 만들고 필요에 따라 조절하는 것이 좋다.
솔직한 후기
1. 일단 없는게 굉장히 많았다.
enum 없다.
클래스 없다.
interface와 구조체를 활용하면 된다는데 그것보단 한 클래스 안에 상태와 메소드를 둘 다 정의하는게 훨씬 안 헷갈리고 편하다.
삼항연산자도 없다. 삼항연산자는 잘쓰면 그 자리에 값을 할당할 수 있어서 정말 편했다.
go func()를 사용해서 비동기 제어를 하는게 잘 눈에 들어오지 않는다.
예를 들어서 js/ts라면 async await 를 쓰면 알아서 된다.
async func() {
try{
await 비동기작업
await 비동기작업
await 비동기작업
await 비동기작업
await 비동기작업
await 비동기작업
} catch(error) {
// 에러 핸들링, 로깅
}
}
저렇게만 써도 비동기 작업의 순서를 제어할 수 있다.
물론 js/ts는 콜스택에서 싱글스레드로 동작한다. golang 은 애초에 멀티 스레드를 경량화해서 동작하기 때문에 이런것은 장점이다.
하지만 이 장점을 생각하면 차라리 코틀린을 쓰는게 낫겠다는 생각이 들었다.
코틀린을 쓰면 일단 자바에서 하는 일은 다 할 수 있다. 자바가 그동안 쌓아온 서비스 개발의 레퍼런스를 모두 활용할 수 있다.
거기에 코루틴도 쓸 수 있다. 코루틴과 고루틴은 굉장히 비슷하다.
코틀린을 쓰면 클래스, 인터페이스 모두 다 쓸 수 있다.
json 을 다루는 부분에서도 굉장히 별로였다.
golang 은 encoding/json 패키지의 Marshal, Unmarshal 을 활용해서 데이터를 다루게 된다.
byte[] 로 형변환을 한 후에 처리하게 되는데 이게 되게 번거롭다.
js 였다면 언어차원에서 json을 지원한다.
서버로 들어온 데이터를 그냥 읽으면 된다.
kotlin 같은 경우엔 java의 auto mapper 를 쓰거나 하면 편하게 쓸 수 있다.
그동안 생겨난 라이브러리의 숫자를 봐도 golang 과 jvm은 비교가 안된다.
npm 패키지는 35만개가 넘는 라이브러리가 있다.
이걸 왜 쓰는지 잘 이해가 되지 않는 느낌이었다.
고루틴과 코틀린 코루틴 사이의 성능 차이를 안다면 더 확실한 의견을 가질 수 있을 것 같다
'프로그래밍' 카테고리의 다른 글
리액트 useMemo 사용하기 (0) | 2021.05.06 |
---|---|
개발자의 이력서 작성하기 (1) | 2021.04.26 |
기술 면접에 자주 나오는 질문들 - 인덱스 (0) | 2021.04.23 |
typescript 로 express, koajs 프로젝트 세팅하다 찾은 좋은 도구 ts-node (1) | 2021.01.30 |
golang 마샬링 / 언마샬링 (0) | 2020.09.17 |
댓글