Hello Ocean! ๐ŸŒผ

[Go] ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์„ ๊ฐ์ง€ํ•ด๋ณด์ž, Data Race Detector ๋ณธ๋ฌธ

Go

[Go] ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์„ ๊ฐ์ง€ํ•ด๋ณด์ž, Data Race Detector

bba_dda 2024. 8. 23. 16:45
๋ฐ˜์‘ํ˜•

์„œ๋ก 

๋™์‹œ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ(Concurrent System)์—์„œ ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์€ ๋งค์šฐ ํ”ํ•˜๊ณ , ๋””๋ฒ„๊น…ํ•˜๊ธฐ ํž˜๋“  ๋ฌธ์ œ์ด๋‹ค.

Go์—์„œ๋Š” ์ด๋Ÿฐ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ๋•๊ธฐ ์œ„ํ•œ built-in Data Race Detector๊ฐ€ ์กด์žฌํ•œ๋‹ค! (์•ผํ˜ธ๐ŸŽ‰)

 

์–ด๋–ป๊ฒŒ ์“ฐ๋‚˜์š”?

์‚ฌ์šฉ๋ฐฉ๋ฒ•์€ ๋งค์šฐ ๋‹จ์ˆœํ•˜๋‹ค.

go command์— -race ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค!

$ go test -race mypkg    // to test the package
$ go run -race mysrc.go  // to run the source file
$ go build -race mycmd   // to build the command
$ go install -race mypkg // to install the package

race build tag๋กœ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ

๋ชจ๋“  ์ฝ”๋“œ์™€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด race detector๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋น„ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋Ÿด ๋•Œ๋Š”, race build tag๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค.

ํŒŒ์ผ ๋งจ ์ฒซ๋ฒˆ์งธ ๋ผ์ธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ` // +build !race` : -race ํ”Œ๋ž˜๊ทธ๊ฐ€ ์—†์„ ๋•Œ๋งŒ ๋นŒ๋“œ์— ํฌํ•จ๋จ
    • race ๊ฒ€์‚ฌ๊ฐ€ ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ/ํ…Œ์ŠคํŠธ
  • ` // +build race` : -race ํ”Œ๋ž˜๊ทธ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ๋นŒ๋“œ์— ํฌํ•จ๋จ
    • race ๊ฒ€์‚ฌ๋งŒ์„ ์œ„ํ•œ ์ฝ”๋“œ/ํ…Œ์ŠคํŠธ

 

์–ด๋–ป๊ฒŒ ์•Œ๋ ค์ฃผ๋‚˜์š”?

Race Decector๊ฐ€ race๋ฅผ ๋ฐœ๊ฒฌํ•˜๋ฉด, report๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

 

report๋Š” ์•„๋ž˜์˜ ๋‚ด์šฉ๋“ค์„ ํฌํ•จํ•œ๋‹ค.

  1. ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€:
  2. ์ฝ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ goroutine์˜ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค:
    • ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ ํŒŒ์ผ/๋ผ์ธ ์ •๋ณด ํฌํ•จ
    • ์˜ˆ์‹œ:
      net.(*pollServer).AddFD()
      src/net/fd_unix.go:89 +0x398
  3. ์ด์ „ ์“ฐ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ goroutine์˜ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค:
    • ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ ํŒŒ์ผ/๋ผ์ธ ์ •๋ณด ํฌํ•จ
    • ์˜ˆ์‹œ:
      net.setWriteDeadline()
      src/net/sockopt_posix.go:135 +0xdf
  4. ์ฝ๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ goroutine์˜ ์ƒ์„ฑ ์œ„์น˜:
    • ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ ํŒŒ์ผ/๋ผ์ธ ์ •๋ณด ํฌํ•จ
    • ์˜ˆ์‹œ:
      net.func·061()
      src/net/timeout_test.go:609 +0x288
  5. ์“ฐ๊ธฐ๋ฅผ ์ˆ˜ํ–‰ํ•œ goroutine์˜ ์ƒ์„ฑ ์œ„์น˜:
    • ํ•จ์ˆ˜ ํ˜ธ์ถœ๊ณผ ํŒŒ์ผ/๋ผ์ธ ์ •๋ณด ํฌํ•จ
    • ์˜ˆ์‹œ:
      net.TestProlongTimeout()
      src/net/timeout_test.go:618 +0x298

report ์˜ˆ์‹œ

==================
WARNING: DATA RACE
Write at 0x00c000098180 by goroutine 6:
  runtime.mapaccess2_faststr()
      /opt/homebrew/Cellar/go/1.20.2/libexec/src/runtime/map_faststr.go:108 +0x42c
  main.main.func1()
      /Users/bada/Projects/Concurrency-in-Go/main.go:67 +0x48

Previous write at 0x00c000098180 by main goroutine:
  runtime.mapaccess2_faststr()
      /opt/homebrew/Cellar/go/1.20.2/libexec/src/runtime/map_faststr.go:108 +0x42c
  main.main()
      /Users/bada/Projects/Concurrency-in-Go/main.go:70 +0xf4

Goroutine 6 (running) created at:
  main.main()
      /Users/bada/Projects/Concurrency-in-Go/main.go:66 +0xd8
==================
Found 1 data race(s)

 

report๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ ๋ฌธ์ œ๊ฐ€ ์–ด๋””์—์„œ ์–ด๋–ป๊ฒŒ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์–‘ํ•œ report๊ด€๋ จ option๋“ค

GORACE ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด race detector์— ์˜ต์…˜๋“ค์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์˜ต์…˜ ๋ชฉ๋ก์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. log_path (default : stderr)
    • report ์ž‘์„ฑ ์œ„์น˜
    • stderr, stdout๋กœ ์„ค์ •ํ•˜๋ฉด ํ‘œ์ค€์—๋Ÿฌ, ํ‘œ์ค€์ถœ๋ ฅ์œผ๋กœ ์ž‘์„ฑ๋จ
    • ํŠน์ • ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด file_name.pid ํ˜•์‹์˜ ํŒŒ์ผ์— ์ž‘์„ฑ๋จ
  2. exitcode (default : 66)
    • ๊ฒฝํ•ฉ์ƒํƒœ ๊ฐ์ง€ ํ›„ ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋  ๋•Œ ์‚ฌ์šฉํ•  exit status๋ฅผ ์„ค์ •
  3. strip_path_prefix (default : "")
    • report์— ํฌํ•จ๋  ๋ชจ๋“  ํŒŒ์ผ ๊ฒฝ๋กœ์—์„œ ์ƒ๋žตํ•  prefix ์ง€์ •
  4. history_size (default : 1)
    • ๊ฐ goroutine์˜ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํžˆ์Šคํ† ๋ฆฌ๋Š” 32K * 2**history_size ๋กœ ๊ตฌ์„ฑ๋จ
    • ์ด ๊ฐ’์„ ์ฆ๊ฐ€์‹œํ‚ค๋ฉด ์Šคํƒ์„ ๋ณต์›ํ•˜์ง€ ๋ชปํ•จ(failed to restore the stack)์—๋Ÿฌ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ฆ๊ฐ€ํ•จ
  5. halt_on_error (default : 0)
    • ์ฒซ ๋ฒˆ์งธ race๋ฅผ ํƒ์ง€ํ•˜๊ณ  ๋ฐ”๋กœ ์ข…๋ฃŒํ• ๊ฒƒ์ธ์ง€, ์ด์–ด์„œ ๊ณ„์† ํƒ์ง€ํ•  ๊ฒƒ์ธ์ง€ ์„ค์ •
    • 0: ์ข…๋ฃŒํ•˜์ง€ ์•Š์Œ / 1: ์ข…๋ฃŒํ•จ
  6. atexit_sleep_ms (default : 1000)
    • ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋˜๊ธฐ ์ „์— main goroutine์—์„œ ๋Œ€๊ธฐํ•  ์‹œ๊ฐ„(ms)์„ ์„ค์ •

์‚ฌ์šฉ ์˜ˆ์‹œ

  GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race

 

Test๋ฅผ ํ†ตํ•œ raceํƒ์ง€์˜ ํ•œ๊ณ„์™€ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

go test -race๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ race detector๋ฅผ ์‹คํ–‰ํ•  ๊ฒฝ์šฐ, ์‹คํ–‰์ค‘์— ๋ฐœ์ƒํ•˜๋Š” race๋งŒ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

(== ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ ˆ์ด์Šค๋Š” ์ฐพ์„ ์ˆ˜ ์—†์Œ)

 

๋”ฐ๋ผ์„œ, test coverage๊ฐ€ ์ถฉ๋ถ„ํžˆ ๋†’์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ,
-raceํ”Œ๋ž˜๊ทธ๋กœ buildํ•œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์‹ค์ œ์™€ ์œ ์‚ฌํ•œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•˜๋ฉด ๋” ๋งŽ์€ race๋ฅผ ํƒ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Runtime Overhead

race detector๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๊ณผ ์‹คํ–‰ ์‹œ๊ฐ„์ด ํฌ๊ฒŒ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  race detector๋กœ ์ธํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€๋Š” runtime.ReadMemStats๋‚˜ runtime/pprof์™€ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ์—์„œ๋Š” ํ™•์ธํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

  1. ์ผ๋ฐ˜์ ์ธ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด 5๋ฐฐ ~ 10๋ฐฐ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ณ , ์‹คํ–‰ ์‹œ๊ฐ„์€ 2๋ฐฐ ~ 20๋ฐฐ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
  2. race dector๋Š” defer ๋ฐ recover ๋ฌธ๋งˆ๋‹ค ์ถ”๊ฐ€๋กœ 8๋ฐ”์ดํŠธ๋ฅผ ํ• ๋‹น
    • defer ๋ฐ recover๊ฐ€ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ํ”„๋กœ๊ทธ๋žจ์€ ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋จ
  3. ์ด๋Ÿฌํ•œ ์ถ”๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น์€ ๊ณ ๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋˜๊ณ  ๋‚˜์„œ ํšŒ์ˆ˜๋จ
    • ๊ณ ๋ฃจํ‹ด์ด ์˜ค๋žซ๋™์•ˆ ์‹คํ–‰๋  ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๊ณ„์† ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
      ==> ์žฅ๊ธฐ๊ฐ„ ์‹คํ–‰๋˜๋Š” ๊ณ ๋ฃจํ‹ด์ด ์ฃผ๊ธฐ์ ์œผ๋กœ defer์™€ recover ํ˜ธ์ถœ์„ ํ•˜๋Š” ๊ฒฝ์šฐ, ํ”„๋กœ๊ทธ๋žจ์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋ฌดํ•œํžˆ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ํ•˜์ž.

 

 

์•„๋ž˜ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•œ ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค.

https://go.dev/doc/articles/race_detector

 

Data Race Detector - The Go Programming Language

Data Race Detector Introduction Data races are among the most common and hardest to debug types of bugs in concurrent systems. A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write. See the

go.dev

 

๋ฐ˜์‘ํ˜•

'Go' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Go] go-socket.io์˜ Redis Adapter  (0) 2022.03.03