Go언어로 개발하면서 사용할 수 있는 유용한 정보와 기법들을 소개한다.
time.Time의 시간을 변경할 때 결과 값을 대입하여 갱신한다
time.Time 타입의 변수 t가 있을 때, 한 시간 이후의 시간을 구하려면 t.Add(time.Hour)을 호출하면 된다. 이때 t의 시간은 Add에 의해서 변경되지 않으며 Add의 return값을 t에 다시 대입하는 것으로 한 시간이 추가된 시간값을 유지할 수 있다. Add의 결과를 별도의 변수에 할당하거나 갱신이 목적이 아닌 경우에는 해당하지 않는다.
t := time.Now()
//Hour를 1 늘리기
t.Add(time.Hour * 1) // t의 시간값을 변경하지 않음
t = t.Add(time.Hour * 1) // t의 시간값을 변경
map은 race condition(동시 접근 가능성)을 유발할 수 있으며, 고루틴에 의해 race condition을 배제하기 위해서 mutex를 사용하거나 sync.Map을 사용한다
m := map[string]string{}
f := func() {
for {
m["key"] = "value"
}
}
go f()
go f()
//fatal error: concurrent map writes 에러 발생
//(1) Mutex 사용
mu := sync.Mutex{}
f := func() {
for {
mu.Lock()
m["key"] = "value"
mu.Unlock()
}
}
//(2) sync.Map 사용
m := sync.Map{}
f := func() {
for {
m.Store("key", "value")
}
}
map은 순서를 보장하지 않는다
Go언어의 기본 key:value 자료 형식인 map은 java의 HashMap과 유사하다. java에서는 LinkedHashMap 등을 사용하여 입력한 순서를 보장하고 keys를 순차적으로 사용할 수 있지만 Go언어 기본 map에서는 이를 지원하지 않는다.
m := map[string]interface{}{
"A": 1,
"B": 1.0,
"C": true,
}
for k, v := range m {
fmt.Sprintln(k, v)
}
// Output 아래의 순서를 보장하지 않음
// A, 1
// B, 1.0
// C, true
len(nil)은 panic이 발생하지 않는다
초기화 되지 않았거나(길이가 0), 배열이 nil인 경우 len 함수는 0을 리턴한다. 별도로 len함수에 전달할 변수에 대해 요소가 있는지 없는지 확인할 때 nil 체크를 하지 않아도 된다.
var list []string
if len(list) == 0{ // len(nil)은 0, panic이 발생하지 않음
fmt.Println("list size is empty")
}
/* 아래와 같이 조건(condition)을 사용하지 않아도 됨
if list == nil || len(list) == 0{
fmt.Println("list size is empty")
}
*/
다음 로직 실행까지 지연을 주고 싶으면 time.Sleep을 사용, 특정 Duration마다 반복 실행할 경우 Ticker를 사용
for { // 아래와 같이 하면 정확한 초 단위 실행을 보장할 수 없음
time.Sleep(time.Second)
//Something to do
}
// 아래와 같이 하면 매 초마다 로직을 실행할 수 있음
for range tick.C {
//Something to do
}
고루틴은 시작시간이 Sequential(순차적) 하지 않다
go func(){
fmt.Println("A")
}()
fmt.Println("B")
go func(){
fmt.Println("C")
}()
// output이 A -> B -> C 순서로 나오지 않을 수 있다
시간 포맷 파싱할 때 Location 정보를 주지 않으면 time Location은 UTC가 됨
// time.Parse를 사용하면 Location 정보를 포함하지 않음
// 그러므로 한국 시간을 기준으로 time을 다룰경우 Location 정보를 포함하는 것이 좋음
fmt.Println(time.ParseInLocation("2006-01-02", "2033-01-01", time.Local))
fmt.Println(time.Parse("2006-01-02", "2033-01-01"))
//output
//
// 2023-01-01 00:00:00 +0900 KST <nil>
// 2023-01-01 00:00:00 +0000 UTC <nil>
map의 value type이 nil-able 이 아닌 경우, key가 없는 경우에도 key를 추가하며 값을 연산할 수 있음
m := map[string]int{}
//아래와 같이 key의 유무를 확인하지 않아도 된다
/* 필요하지 않음
if _, ok := m["key"]; !ok{
m["key"] = 0
}
*/
m["key"]++
//time.Duration의 경우 int64를 재정의한 type이므로 아래와 같이 사용할 수도 있다.
tm := map[string]time.Duration{}
m["key"] += time.Second
context.Context의 완료(Done) 전파 방향은 parent → child[ren] 방향이며 child → parent의 완료 전파는 동작하지 않음
parent, parentCancel := context.WithCancel(context.Background())
defer parentCancel()
child, childCancel := context.WithCancel(parent)
defer childCancel()
// childCancel()
/* Output
2023/10/11 11:10:01 Parent Incomplete
2023/10/11 11:10:01 Child Done
*/
// parentCancel()
/* Output
2023/10/11 11:04:05 Parent Done
2023/10/11 11:04:05 Child Done
*/
select {
case <-parent.Done():
log.Println("Parent Done")
case <-time.After(time.Second):
log.Println("Parent Incomplete")
}
select {
case <-child.Done():
log.Println("Child Done")
case <-time.After(time.Second):
log.Println("Parent Incomplete")
}
OS(또는 플랫폼)에 따라 파일 경로를 결합(Combine)할 땐 filepath.Join을 사용하고, URL과 같이 상시 /로 구분자를 사용해야 하는 경우 path.Join을 사용한다
fmt.Println(filepath.Join("path", "to", "file"))
// Output
// Linux: path/to/file
// Windows: path\to\file
fmt.Println(path.Join("path", "to", "file"))
// Output
// path/to/file
함수 중복 실행 방지를 위해 channel을 사용하는 간단한 기법
var channel = make(chan struct{}, 1)
func myFunction() {
select {
case channel <- struct{}{}:
defer func() { <-channel }()
// 함수 로직
default:
// 함수가 이미 실행 중
}
}