동시성은 현대 소프트웨어 개발에서 필수적인 역할을 하며, 프로그램이 여러 작업을 동시에 관리할 수 있도록 하여 성능, 응답성 및 리소스 활용도를 향상시킵니다.

바둑의 광범위한 수용은 본질적으로 사용자 친화적이며 동시 프로그래밍 작업 중에 일반적으로 발생하는 경쟁 조건 및 교착 상태와 같은 잠재적 문제를 피할 수 있는 복잡하지 않은 수단을 제공하는 동시성 기능에 기인할 수 있습니다.

Go의 동시성

프로그래밍 언어 Go는 표준 라이브러리 및 개발 도구에 완전히 통합된 동시 연산을 위한 광범위한 기능을 제공합니다. Go에서 동시 실행은 고루틴과 채널을 통해 이루어집니다.

고루틴은 동일한 메모리 공간 내에서 다른 고루틴과 함께 동시에 작동하는 오버헤드가 낮고 독립적인 기능 단위입니다. 고루틴을 활용하면 스레드를 명시적으로 관리할 필요 없이 여러 프로세스를 동시에 진행할 수 있습니다. 운영 체제 스레드에 비해 고루틴은 상대적으로 가볍기 때문에 수천 개에서 수백만 개에 이르는 수많은 고루틴을 병렬로 효율적으로 실행할 수 있습니다.

채널은 동시 실행 중인 고루틴 간에 데이터를 조정하고 공유하기 위한 통신 매체 역할을 합니다. 채널은 본질적으로 특정 유형의 값을 전송하도록 설계된 조직화된 파이프라인으로, 고루틴이 특정 프로토콜을 따라 안전하게 정보를 교환할 수 있도록 해줍니다. 이러한 채널은 경합 조건 및 기타 유사한 동시성 문제와 같은 잠재적인 문제를 방지하여 고루틴 간 안전성을 보장하는 데 중요한 역할을 합니다.

바둑에서 고루틴과 채널을 활용하면 안전성과 성능을 보장하면서 멀티코어 프로세서에서 효율적으로 실행할 수 있는 강력하고 단순화된 동시 프로그래밍 모델이 제시됩니다. 이러한 리소스를 통해 고성능 및 반응성이 뛰어난 애플리케이션을 쉽게 만들 수 있습니다.

동시 코드 실행을 위해 고루틴을 사용하는 방법

Go 런타임이 고루틴 관리를 담당한다는 점을 고려할 때, 각 고루틴이 고유한 전용 스택을 가지고 있다는 점은 주목할 만합니다. 이 설계 기능 덕분에 고루틴은 초기 스택 크기가 킬로바이트 단위로 측정되는 비교적 작고 가벼운 메모리 풋프린트를 자랑합니다.

이 글도 확인해 보세요:  Rust의 제네릭 형식 알아보기

Go 런타임은 효율적인 작업 분배를 통해 고루틴을 다양한 운영 체제 스레드에 멀티플렉싱하여 제한된 수의 스레드에서 여러 고루틴을 동시에 실행할 수 있도록 합니다.

고루틴 생성을 선언할 때 함수 호출 앞에 ‘go’ 키워드를 사용하면 단일 프로그램 내에서 동시에 실행할 여러 작업을 지정하는 효과적인 방법이 됩니다.

 func main() {
    go function1() // Create and execute goroutine for function1
    go function2() // Create and execute goroutine for function2

    // ...
}

func function1() {
    // Code for function1
}

func function2() {
    // Code for function2
}

프로그램에서 go 키워드를 사용하여 function1() 및 function2()를 호출하면 Go 런타임은 두 함수를 고루틴으로 동시에 실행합니다.

콘솔에 텍스트를 인쇄하기 위해 고루틴이 활용되는 사례를 알려주시겠어요?

 package main

import (
    "fmt"
    "time"
)

func printText() {
    for i := 1; i <= 5; i++ {
        fmt.Println("Printing text", i)
        time.Sleep(1 * time.Second)
    }
}

func main() {
    go printText() // Start a goroutine to execute the printText function concurrently

    // Perform other tasks in the main goroutine
    for i := 1; i <= 5; i++ {
        fmt.Println("Performing other tasks", i)
        time.Sleep(500 * time.Millisecond)
    }

    // Wait for the goroutine to finish
    time.Sleep(6 * time.Second)
}

`printText` 함수는 앞서 언급한 반복 메커니즘을 활용하며, 각 반복 사이에 1초의 시간 경과를 두고 텍스트가 여러 번 배포되며 `time` 패키지가 이를 용이하게 합니다.

주 목적은 go printText 함수를 활성화하여 보조 루틴을 시작하여, 인쇄 텍스트 함수가 주 함수 내의 나머지 코드와 동시에 실행될 수 있도록 하는 독립적인 동시 서브 루틴으로 인쇄 텍스트 함수를 디스패치한다.

인쇄 텍스트 이동 루틴이 완료되기 전에 프로그램이 조기에 종료되는 것을 방지하기 위해, 주 이동 루틴이 6초 동안 일시 중지되도록 하는 시간.수면 함수를 사용하여 지연 기간이 도입됩니다. 이 기법은 채널 및 대기 그룹과 같은 동기화 메커니즘을 사용하여 고루틴의 활동을 조정할 수 있는 여러 작업의 동시 실행 동작을 시뮬레이션하는 데 실용적인 솔루션으로 사용됩니다.

이 글도 확인해 보세요:  판다와 폴라: 성능 대결

통신 및 동기화를 위한 채널 사용

고루틴을 사용한 동시 프로그래밍은 채널을 통해 통신하고 조정하는 고루틴 고유의 기능으로 인해 용이하지만, 기존 스레딩은 구현하기 어려울 수 있는 잠금 및 세마포어와 같은 명시적 동기화 기법을 사용해야 합니다.

채널은 독립적인 실행 스레드 간에 데이터가 흐르는 통로로 간주할 수 있습니다. 한 스레드가 채널을 통해 값을 전송하면 다른 스레드가 채널에서 동일한 값을 수신할 수 있습니다. 이 시스템은 데이터 전송이 안전하고 조율되도록 보장합니다.

채널을 통한 정보 전송 및 수신에 “<-" 연산자를 사용합니다.

다음 구절을 형식적이고 정교한 방식으로 더 설득력 있게 표현할 수 있습니까? “이 그림에는 두 개의 비동기 고루틴 간의 스레드 간 통신을 위한 채널의 기본 적용을 보여주는 예가 있습니다.

 func main() {
    // Create an unbuffered channel of type string
    ch := make(chan string)

    // Goroutine 1: Sends a message into the channel
    go func() {
        ch <- "Hello, Channel!"
    }()

    // Goroutine 2: Receives the message from the channel
    msg := <-ch
    fmt.Println(msg) // Output: Hello, Channel!
}

기본 함수에서 create() 연산을 사용하여 “ch”라는 버퍼링되지 않은 채널이 설정됩니다.그런 다음 두 개의 독립적인 코루틴이 시작되는데, 첫 번째 코루틴은 연산자보다 작은 연산자를 통해 채널로 “Hello, Channel!” 통신을 전송하고, 후속 코루틴은 동일한 연산자를 통해 채널에서 메시지를 가져옵니다. 궁극적으로 기본 루틴은 획득한 메시지를 콘솔에 표시합니다.

다양한 채널 타이핑의 활용은 설정 시 사양을 통해 설명할 수 있습니다. 이 기능의 데모는 아래에 나와 있습니다:

 func main() {
    // Unbuffered channel
    ch1 := make(chan int)

    // Buffered channel with a capacity of 3
    ch2 := make(chan string, 3)

    // Sending and receiving values from channels
    ch1 <- 42 // Send a value into ch1
    value1 := <-ch1 // Receive a value from ch1

    ch2 <- "Hello" // Send a value into ch2
    value2 := <-ch2 // Receive a value from ch2
}

채널 1은 수정되지 않은 정수 채널이고, 채널 2는 최대 용량을 가진 버퍼링된 문자열 채널입니다. 이 채널을 통해 “<-" 기호를 사용하여 값을 송수신할 수 있지만, 전송되는 데이터가 지정된 데이터 유형을 준수해야 한다는 조건이 있습니다.

이 글도 확인해 보세요:  Nextra를 사용하여 Next.js로 기술 문서 사이트 구축하기

채널은 작업 중 차단된 특성을 활용하여 고루틴의 실행을 동기화하는 수단으로 사용됩니다.

 func main() {
    ch := make(chan bool)

    go func() {
        fmt.Println("Goroutine 1")
        ch <- true // Signal completion
    }()

    go func() {
        <-ch // Wait for the completion signal from Goroutine 1
        fmt.Println("Goroutine 2")
    }()

    <-ch // Wait for completion signal from Goroutine 2
    fmt.Println("Main goroutine")
}

기본 함수에는 두 개의 동시 고루틴이 존재합니다. 첫 번째 고루틴은 “ch”라는 채널을 통해 “true”라는 부울 값을 전송하여 작업을 완료합니다. 두 번째 고루틴은 채널에서 이 완료 신호를 수신할 때까지 유휴 상태로 있다가 실행을 진행합니다. 결국 1차 고루틴은 2차 고루틴의 완료 신호를 기다렸다가 종료합니다.

Gin으로 Go에서 웹 애플리케이션을 구축할 수 있습니다.

Go와 Gin 프레임워크를 사용하여 Go의 동시 기능을 활용하여 고성능 웹 애플리케이션을 구축할 수 있습니다.

Gin을 활용하여 HTTP 라우팅 및 미들웨어를 능숙하게 관리할 수 있습니다. 데이터베이스 쿼리, API 호출 또는 차단이 필요한 기타 작업과 같은 작업에 고루틴과 채널을 활용하여 Go의 고유한 동시 기능을 활용하세요.

By 김민수

안드로이드, 서버 개발을 시작으로 여러 분야를 넘나들고 있는 풀스택(Full-stack) 개발자입니다. 오픈소스 기술과 혁신에 큰 관심을 가지고 있고, 보다 많은 사람이 기술을 통해 꿈꾸던 일을 실현하도록 돕기를 희망하고 있습니다.