원 질문글: go언어 공부중인데 이거 왜 실행이 안될까요?

https://gall.dcinside.com/mgallery/board/view/?id=gophers&no=305&page=1



1. 경쟁 상태란?

책을 보면

-여러 개의 스레드나 고루틴이 특정 리소스에 접근하려고 경쟁하는 상태

-두 개 이상의 명령이 동일한 메모리 주소에 접근하는 상황에서 쓰기 연산이 수행될 때 발생하는 상태

이렇게 나와 있고(<Go 마스터하기> 2판), 다른 곳에서도 대부분 비슷하게 설명하는 것 같습니다.


이게 왜 문제가 되는지 부연설명을 하자면, 하나의 명령에서 공통 리소스(변수, 메모리..)에 대한 변경 사항을 만들었음에도 불구하고, 동시적으로 진행되는 다른 명령에는 변경 사항이 적용되지 않는 경우가 발생할 수 있기 때문이라고 할 수 있겠습니다.

올라왔던 예제를 이용해서 설명해 보겠습니다.


account.Balance+=1000 이라는 구문을 풀어 써보면, 1) account.Balance에 지정된 어떤 메모리 A에서 값을 받아오고, 2) 그 값에 1000을 더한 후 3) 다시 메모리 A로 값을 반환하는 세 가지 과정을 거치는 명령이라고 볼 수 있습니다.

이는 다음과 같이 나타낼 수 있습니다.





이런 명령이 연속적으로 두 번 일어나면 다음과 같습니다.

첫 번째 명령에 의한 변경사항이 A에 저장돼 두 번째 명령으로 넘어갑니다.





그럼 이 명령이 동시에 두 번 일어나면 어떻게 될까요?

이 경우 첫 번째 명령으로 인한 변경사항이 A에 저장되기 전에, 두 번째 명령이 A로부터 값을 받아옵니다.

그래서 두 번째 명령은 첫 번째 명령 결과와는 무관하게 수행됩니다.





이렇게 혹은





이렇게 되는 것도 가능하겠네요.

이 예시에서는 첫 번째 명령의 결과를 두 번째 명령이 무시하고 덮어쓴 꼴이 되었습니다.





1000을 더하는 고루틴 여러 개를 실행하다 보면, 가끔 위와 같이 동시에 실행되는 경우가 발생해, 실제보다 덜 더해지는 상황이 발생합니다.
원 질문글에서의 예제도 이런 상황이 발생하는 걸 노려서, 2000이 더해져야 하는데 1000만 더해져 나중에 합이 0 미만이 되는 걸 보여주려는 것이고요.
다만 이는 1000을 빼는 경우에도 마찬가지이기 때문에, 오히려 2000이 빼져야 하는데 1000만 빼지는 경우도 발생할 수 있습니다.
그래서 이 예제로는 확정적으로 panic이 나오지는 않습니다.
하지만 동시에 실행되는 경우를 확인하는 것만이 목적이라면, 코드를 살짝 수정해서 더 쉽게 panic을 확인할 수 있습니다.

.....
func DepositAndWithdraw(account *Account) {

if account.Balance < 0 {

panic(fmt.Sprintf("Balance should not be negative value: %d", account.Balance))

}
.....
에서 if account.Balance < 0을 if account.Balance < 0 || account.Balance>10000 이라는 조건으로 바꿔주면 됩니다.
-1000이나 11000이나 동시에 실행되는 경우가 발생해야 나올 수 있는 값들이니까요.






p.s. race condition에 대해 잘 정리해둔 글이 있어서 링크 남깁니다.