GoLang 활용
🙊

GoLang 활용

Created
May 27, 2024 09:36 AM
Last edited time
Last updated May 28, 2024
Tags
Language
Language
GoLang
URL

Intro::

GoLang 에서 활용할 수 있는 다양한 문법에 대해서 알아보자.
 

Go 루틴 생성

Go 루틴은 Go 언어 에서 비동기적으로 코드를 실행하는 경량 스레드입니다. Go 루틴은 함수 또는 메서드를 병렬로 실행할 수 있게 해주며, 이를 통해 동시성을 쉽게 처리할 수 있습니다.
package main import ( "fmt" "time") func say(s string) { for i := 0; i < 10; i++ { fmt.Println(s, "***", i) time.Sleep(time.Second * 1) } } func main() { // 함수를 비동기적으로 실행 go say("Async1") go say("Async2") go say("Async3") // 3초 대기 time.Sleep(time.Second * 10) }
 

Go 루틴 대기

package main import ( "fmt" "runtime" "sync" ) type counter struct { i int64 mu sync.Mutex // 공유 데이터 i를 보호하기 위한 뮤텍스 } // counter 값을 1씩 증가시킴 func (c *counter) increment() { c.mu.Lock() // i 값을 변경하는 부분(임계 영역)을 뮤텍스로 잠금 c.i += 1 // 공유 데이터 변경 c.mu.Unlock() // i 값을 변경 완료한 후 뮤텍스 잠금 해제 } // counter의 값을 출력 func (c *counter) display() { fmt.Println(c.i) } func main() { // 모든 CPU를 사용하게 함 runtime.GOMAXPROCS(runtime.NumCPU()) c := counter{i: 0} // 카운터 생성 wg := sync.WaitGroup{} // WaitGroup 생성 // c.increment()를 실행하는 고루틴 1000개 실행 for i := 0; i < 1000; i++ { wg.Add(1) // WaitGroup의 고루틴 개수 1 증가 go func() { defer wg.Done() // 고루틴 종료 시 Done() 처리 c.increment() // 카운터 값을 1 증가시킴 }() } wg.Wait() // 모든 고루틴이 종료될 때까지 대기 c.display() // c의 값 출력 }
  • runtime.GOMAXPROCS(n) 함수는 Go 런타임이 동시에 사용할 수 있는 최대 CPU 코어의 개수를 설정합니다. 여기서 n은 사용할 코어의 수입니다.
 

채널과 채널 버퍼링

채널은 Go 루틴에서 함수들 사이에 데이터를 주고 받는데 이용됩니다. 데이터를 받을 때 까지 대기하여, 동기화를 구현하는 데 이용할 수 있습니다.
Go에서 채널은 기본적으로 버퍼를 사용하지 않고 생성됩니다. make() 함수에 인자를 추가하여 버퍼를 생성할 수 있습니다. 버퍼를 이용할 때는 채널에 데이터를 수신 후 다른 Go 루틴에서 데이터를 수신하지 않아도 다른 작업을 진행할 수 있습니다.
package main func main() { // 정수형 채널을 생성한다 ch1 := make(chan int) go func() { ch1 <- 123 //채널에 123을 보낸다 }() var i int i = <-ch1 // 채널로부터 123을 받는다 println(i) // ch <- 1// 수신 루틴이 없어 데드락 발생 // println(<- ch)// 코멘트만 해도 데드락 ch2 := make(chan int, 1)// 버퍼 설정 ch2 <- 101 println(<- ch2) }
 

채널 송신/수신 지정

Go 에서 채널은 파라미터로 전달될 때 참조값으로 전달되어서 함수에 전달해도 채널을 여러 고루틴이나 함수에서 안전하게 사용할 수 있습니다.
package main import "fmt" func main() { ch := make(chan string, 1) sendChan(ch) receiveChan(ch) } func sendChan(ch chan<- string) {// ch 에서 송신만 ch <- "Data" // x := <-ch // 에러발생 } func receiveChan(ch <-chan string) {// ch 에서 수신 data := <-ch fmt.Println(data) }
 

채널 닫기

package main func main() { ch := make(chan int, 2) // 채널에 송신 ch <- 1 ch <- 2 // 채널을 닫는다 close(ch) // 채널 수신 println(<-ch) println(<-ch) // 방법1 // 채널이 닫힌 것을 감지할 때까지 계속 수신 if _, success := <-ch; !success { println("더이상 데이타 없음.") } // 방법2 // 위 표현과 동일한 채널 range 문 for i := range ch { println(i) } }
 

채널 셀렉트

Go 에서 select문은 switch문과 유사하면서도 조금 다르다. select문은 채널 통신에 사용되며, 여러 채널 연산 중 하나를 선택하여 실행합니다. select문은 채널 연산이 준비될 때까지 블록되며, 여러 채널 연산이 준비된 경우 랜덤하게 선택합니다.
package main import "time" func main() { done1 := make(chan bool) done2 := make(chan bool) go run1(done1) go run2(done2) EXIT:// 라벨 -> 빠르게 루프를 탈출하거나 건너뛰는 용도로 사용한다. for { select { case <-done1: println("run1 완료") case <-done2: println("run2 완료") break EXIT } } } func run1(done chan bool) { time.Sleep(1 * time.Second) done <- true } func run2(done chan bool) { time.Sleep(2 * time.Second) done <- true }
 

IPv4 유효성 체크

package main import ( "regexp" "strings" "fmt" ) func ValidIP4(ipAddress string) bool { ipAddress = strings.Trim(ipAddress, " ") re, _ := regexp.Compile(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`) if re.MatchString(ipAddress) { return true } return false } func main() { IPv4 := "127.0.0.1" fmt.Printf("IPv4[%s] valid: %t", IPv4, ValidIP4(IPv4)) }

정규식 분석

  • ^: 문자열의 시작을 의미합니다.
  • (([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}:
    • ([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):
      • [0-9]: 0에서 9 사이의 숫자 (한 자리 수)
      • [1-9][0-9]: 10에서 99 사이의 숫자 (두 자리 수)
      • 1[0-9]{2}: 100에서 199 사이의 숫자 (세 자리 수)
      • 2[0-4][0-9]: 200에서 249 사이의 숫자 (세 자리 수)
      • 25[0-5]: 250에서 255 사이의 숫자 (세 자리 수)
    • \.: 점(.) 문자를 의미합니다.
    • {3}: 이 패턴이 3번 반복되어야 함을 의미합니다. 이는 IPv4 주소의 첫 세 옥텟(숫자 그룹)을 매칭합니다.
  • ([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):
    • 마지막 옥텟을 매칭합니다. 앞의 패턴과 동일하지만 점(.)이 없습니다.
  • $: 문자열의 끝을 의미합니다.
 

파일 생성 + 읽기/쓰기

package main import ( "fmt" "os" ) func Read(fileName string) { data, err := os.ReadFile(fileName) if err != nil { panic(err) } fmt.Println(string(data)) } func Write(fileName string, message string) { bytes := []byte(message) err := os.WriteFile(fileName, bytes, 644) if err != nil { panic(err) } } func main() { // 파일 생성 fileName := "test.txt" fileInfo, _ := os.Stat(fileName) // 파일 정보 확인 if fileInfo != nil { fmt.Printf("%s deleted\n", fileName) os.Remove(fileName) } // 생성 file, err := os.Create(fileName) if err != nil { panic(err) } defer file.Close() fmt.Printf("%+v\n", fileInfo) Write(fileName, "hi my name is") Read(fileName) }
 

HTTP 요청

package main import ( "io" "net/http" "os" "time" log "github.com/sirupsen/logrus" ) func main() { var request *http.Request var responseBody []byte var err error // 로그 파일 열기 logfileName := "logfile.log" logfile, err := os.OpenFile(logfileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { log.Fatalf("Failed to open log file: %s", err) } defer logfile.Close() log.SetOutput(logfile) // 로그 파일로 지정 log.SetLevel(log.DebugLevel) // 디버그레벨로 기록 설정 requestUrl := "http://www.naver.com" if request, err = http.NewRequest(http.MethodGet, requestUrl, nil); err != nil { log.Errorf("error: %s", err) panic(err) } request.Header.Add("accept", "application/json") query := request.URL.Query() // query.Add("query-key", "query-value") request.URL.RawQuery = query.Encode() client := &http.Client{ Timeout: 30 * time.Second, } // 리퀘스트 요청 response, err := client.Do(request) if err != nil { log.Errorf("error: %s", err) panic(err) } // 함수가 종료 될 때 응답 본문 닫기 defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { panic(err) } }(response.Body) // 응답 본문 읽기 responseBody, err = io.ReadAll(response.Body) if err != nil { log.Errorf("error: %s", err) } log.Debugf("RequestURI: %s", request.URL.String()) log.Debugf("Method: %s", request.Method) log.Debugf("ResponseBody: %s", responseBody) }
 

References::

Loading Comments...