Go

Goroutine에서 `os.Chdir()` 사용할 때 발생하는 문제와 해결 방법

드리프트2 2024. 10. 6. 22:27

Goroutine에서 os.Chdir() 사용할 때 발생하는 문제와 해결 방법

안녕하세요, 여러분.

 

오늘은 Go 언어에서 Goroutine을 사용할 때 주의해야 할 중요한 사항 중 하나인 os.Chdir() 함수에 대해 이야기해보려 합니다.

 

os.Chdir()는 현재 작업 디렉토리를 변경하는 함수인데, 이를 Goroutine과 함께 사용하면 예상치 못한 문제가 발생할 수 있습니다.

 

이 강의에서는 그 이유와 해결 방법을 알아보겠습니다.

 


 

1. Goroutine과 os.Chdir()의 충돌

Goroutine은 Go 언어에서 동시성을 처리하는 매우 효과적인 방법인데요, 여러 Goroutine이 동시에 실행되면서 서로 다른 작업을 할 수 있습니다.

 

그런데 Goroutine에서 os.Chdir()를 호출하면 문제가 발생할 수 있습니다.

 

그 이유를 보기 위해 간단한 예제를 살펴보겠습니다.

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "sync"
    "time"
)

var wg sync.WaitGroup

func main() {
    baseDir, _ := os.Getwd()
    wg.Add(3)
    for i := 1; i < 4; i++ {
        go changeDirAndPrint(baseDir, i)
    }
    wg.Wait()
}

func changeDirAndPrint(baseDir string, num int) {
    defer wg.Done()
    wd, _ := os.Getwd()
    fmt.Printf("Goroutine %d: 시작 디렉토리: %s\n", num, wd)
    os.Chdir(filepath.Join(baseDir, fmt.Sprintf("%d", num)))
    wd, _ = os.Getwd()
    fmt.Printf("Goroutine %d: 변경 후 디렉토리: %s\n", num, wd)
    time.Sleep(1 * time.Second)
    wd, _ = os.Getwd()
    fmt.Printf("Goroutine %d: 깨어난 후 디렉토리: %s\n", num, wd)
}

 

위 코드는 각 Goroutine이 각각 다른 디렉토리로 이동한 뒤, 디렉토리 상태를 출력하는 예제입니다.

 

하지만 실행 결과는 예상과 다르게 모든 Goroutine이 마지막에 같은 디렉토리에 있는 것처럼 동작하는데요.

 

실행 결과:

Goroutine 1: 시작 디렉토리: /Users/example/work
Goroutine 2: 시작 디렉토리: /Users/example/work
Goroutine 3: 시작 디렉토리: /Users/example/work
Goroutine 1: 변경 후 디렉토리: /Users/example/work/1
Goroutine 2: 변경 후 디렉토리: /Users/example/work/2
Goroutine 3: 변경 후 디렉토리: /Users/example/work/3
Goroutine 1: 깨어난 후 디렉토리: /Users/example/work/3
Goroutine 2: 깨어난 후 디렉토리: /Users/example/work/3
Goroutine 3: 깨어난 후 디렉토리: /Users/example/work/3

 

 


 

2. 문제의 원인: os.Chdir()는 프로세스 전역 상태 변경

이 문제의 원인은 Go의 Goroutine이 프로세스 내에서 실행된다는 점에 있습니다.

 

즉, Goroutine은 같은 프로세스의 자원을 공유하는데, 그 중 하나가 현재 작업 디렉토리입니다.

 

os.Chdir()프로세스 전역 상태인 현재 작업 디렉토리를 변경하는 함수이기 때문에, 한 Goroutine에서 디렉토리를 변경하면 다른 Goroutine에도 영향을 미치게 됩니다.

 

Goroutine은 하나의 프로세스에서 여러 개의 경량 스레드처럼 실행되므로, 프로세스 단위로 공유되는 자원(예: 현재 작업 디렉토리)은 독립적으로 유지되지 않습니다.

 

따라서 마지막에 실행된 Goroutine의 디렉토리 변경이 모든 Goroutine에 적용된 것입니다.

 


 

3. 해결 방법: os/exec.Cmd로 독립 프로세스 실행

그렇다면 Goroutine이 각자 독립적으로 다른 디렉토리에서 작업을 수행하도록 하려면 어떻게 해야 할까요?

 

그 해결책은 os/exec.Cmd를 사용하는 것입니다.

 

os/exec.Cmd는 외부 명령을 실행할 때 새로운 프로세스를 생성하여, 각 프로세스가 독립적인 작업 디렉토리를 가질 수 있게 해줍니다.

 

다음 코드는 os/exec.Cmd를 사용하여 각 Goroutine이 독립적인 디렉토리에서 작업을 수행하도록 한 예시입니다.

package main

import (
    "fmt"
    "os/exec"
    "path/filepath"
    "sync"
)

var wg sync.WaitGroup

func main() {
    baseDir := "/path/to/base"
    wg.Add(3)
    for i := 1; i < 4; i++ {
        go runCommandInDir(baseDir, i)
    }
    wg.Wait()
}

func runCommandInDir(baseDir string, num int) {
    defer wg.Done()

    cmd := exec.Command("ls")
    cmd.Dir = filepath.Join(baseDir, fmt.Sprintf("%d", num))

    output, err := cmd.Output()
    if err != nil {
        fmt.Printf("Goroutine %d: 에러 발생 - %v\n", num, err)
        return
    }

    fmt.Printf("Goroutine %d: 출력 결과:\n%s\n", num, output)
}

 

이 코드는 각 Goroutine이 독립적으로 ls 명령을 실행하며, cmd.Dir을 통해 각 프로세스의 작업 디렉토리를 개별적으로 설정합니다.

 

이렇게 하면 각 Goroutine이 독립된 디렉토리에서 작업을 수행할 수 있습니다.

 


 

4. 요약: Goroutine에서 os.Chdir() 사용 시 주의사항

  1. os.Chdir()는 프로세스 전역 상태를 변경합니다. Goroutine은 같은 프로세스 내에서 실행되므로, 한 Goroutine에서 디렉토리를 변경하면 다른 Goroutine에도 영향을 미칩니다.
  2. Goroutine마다 독립적인 디렉토리에서 작업을 수행하려면, os/exec.Cmd를 사용하여 각 작업을 별도의 프로세스로 실행하는 것이 좋습니다.
  3. Goroutine이 서로 다른 디렉토리에서 처리해야 할 작업이 있을 때는, 프로세스 분리가 가장 안전한 접근 방식입니다.

 


이 강의를 통해 Goroutine과 os.Chdir() 사용 시 발생할 수 있는 문제와 그 해결 방법을 배웠습니다.

 

Go 언어의 동시성 처리에서 이러한 전역 상태의 공유는 주의 깊게 다뤄야 하는 부분입니다.

 

여러분도 Goroutine을 사용할 때 이러한 점을 염두에 두고 프로세스 자원을 적절하게 관리하시길 바랍니다.

 

감사합니다.