게시물 일부 발췌입니다. 전체 게시물은 여기에서 볼 수 있습니다: https://victoriametrics.com/blog/go-sync-cond/
이 게시물은 Go의 동시성 처리에 관한 시리즈의 일부입니다.
Go에서 sync.Cond는 동기화 기본 요소이지만 sync.Mutex 또는 sync.WaitGroup과 같은 형제만큼 일반적으로 사용되지는 않습니다. 대부분의 프로젝트나 심지어 다른 동기화 메커니즘이 대신 사용되는 표준 라이브러리에서도 이를 거의 볼 수 없습니다.
즉, Go 엔지니어로서 sync.Cond를 사용하는 코드를 읽고 무슨 일이 일어나고 있는지 전혀 모르고 싶지 않을 것입니다. 왜냐하면 이 코드는 결국 표준 라이브러리의 일부이기 때문입니다.
따라서 이 토론은 격차를 줄이는 데 도움이 될 것이며 더 나아가 실제로 실제로 어떻게 작동하는지 더 명확하게 이해할 수 있게 될 것입니다.
이제 sync.Cond가 무엇인지 자세히 살펴보겠습니다.
고루틴이 일부 공유 데이터 변경과 같은 특정 작업이 발생할 때까지 기다려야 하는 경우 "차단"할 수 있습니다. 즉, 계속 진행하라는 명령을 받을 때까지 작업을 일시 중지한다는 의미입니다. 이를 수행하는 가장 기본적인 방법은 루프를 사용하거나 시간을 추가하는 것입니다. CPU가 바쁜 대기로 인해 미쳐가는 것을 방지하려면 절전 모드를 사용하세요.
다음과 같습니다.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
아무 것도 변경되지 않은 경우에도 해당 루프가 여전히 백그라운드에서 실행되어 CPU 주기를 소모하므로 이는 실제로 효율적이지 않습니다.
여기서 sync.Cond가 개입하여 고루틴이 작업을 조정할 수 있는 더 나은 방법을 제공합니다. 기술적으로 좀 더 학문적인 배경을 갖고 있는 경우에는 "조건 변수"입니다.
sync.Cond가 제공하는 기본 인터페이스는 다음과 같습니다.
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
알겠습니다. 간단한 의사 예시를 확인해 보겠습니다. 이번에는 포켓몬 테마가 진행 중입니다. 특정 포켓몬을 기다리고 있다고 가정하고 포켓몬이 나타나면 다른 고루틴에 알리고 싶습니다.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
이 예에서 하나의 고루틴은 피카츄가 나타날 때까지 기다리는 반면, 다른 고루틴(생산자)은 목록에서 무작위로 포켓몬을 선택하고 새 포켓몬이 나타날 때 소비자에게 신호를 보냅니다.
생산자가 신호를 보내면 소비자는 깨어나서 올바른 포켓몬이 나타났는지 확인합니다. 그렇다면 포켓몬을 잡게 되고, 그렇지 않으면 소비자는 다시 잠에 빠져 다음 포켓몬을 기다립니다.
문제는 신호를 보내는 생산자와 실제로 깨어나는 소비자 사이에 간격이 있다는 것입니다. 그 동안 소비자 고루틴이 1ms보다 늦게 깨어나거나(드물게) 다른 고루틴이 공유 포켓몬을 수정하기 때문에 포켓몬이 변경될 수 있습니다. 따라서 sync.Cond는 기본적으로 다음과 같이 말합니다. '야, 뭔가 달라졌어! 일어나서 확인해 보세요. 너무 늦으면 또 바뀔 수도 있어요.'
소비자가 늦게 일어나면 포켓몬이 도망갈 수 있고, 고루틴은 다시 잠들게 됩니다.
"어, 채널을 이용해 포켓몬 이름이나 신호를 다른 고루틴에 보낼 수 있겠네요"
물론이죠. 실제로 채널은 일반적으로 Go에서 sync.Cond보다 선호됩니다. 그 이유는 채널이 더 간단하고 관용적이며 대부분의 개발자에게 친숙하기 때문입니다.
위의 경우 채널을 통해 쉽게 포켓몬 이름을 보내거나, 데이터를 보내지 않고 빈 구조체를 사용하여{} 신호를 보낼 수 있습니다. 하지만 우리의 문제는 채널을 통해 메시지를 전달하는 것뿐만 아니라 공유 상태를 처리하는 것에도 관한 것입니다.
우리의 예는 매우 간단하지만 여러 고루틴이 공유 포켓몬 변수에 액세스하는 경우 채널을 사용하면 어떤 일이 일어나는지 살펴보겠습니다.
즉, 여러 고루틴이 공유 데이터를 수정하는 경우 이를 보호하기 위해 뮤텍스가 여전히 필요합니다. 이러한 경우 적절한 동기화와 데이터 안전을 보장하기 위해 채널과 뮤텍스의 조합을 자주 볼 수 있습니다.
"그럼 방송신호는요?"
좋은 질문입니다! 실제로 채널을 닫으면(close(ch)) 채널을 사용하여 대기 중인 모든 고루틴에 대한 브로드캐스트 신호를 흉내낼 수 있습니다. 채널을 닫으면 해당 채널에서 수신하는 모든 고루틴이 알림을 받습니다. 하지만 닫힌 채널은 재사용할 수 없다는 점을 명심하세요. 일단 닫힌 채널은 닫힌 상태로 유지됩니다.
그런데 실제로 Go 2에서 sync.Cond를 제거하는 것에 대한 이야기가 있었습니다: Proposal: sync: Cond 유형을 제거합니다.
"그럼 sync.Cond는 뭐에 좋은가요?"
음, sync.Cond가 채널보다 더 적합한 특정 시나리오가 있습니다.
"sync.Cond에 왜 Lock이 내장되어 있나요?"
이론적으로 sync.Cond와 같은 조건 변수는 신호가 작동하기 위해 잠금에 연결될 필요가 없습니다.
사용자가 조건 변수 외부에서 자신의 잠금을 관리하도록 할 수 있는데, 이는 더 많은 유연성을 제공하는 것처럼 들릴 수 있습니다. 실제로는 기술적 한계가 아니라 인간의 실수에 관한 것입니다.
수동으로 관리하면 패턴이 실제로 직관적이지 않기 때문에 쉽게 실수로 이어질 수 있습니다. Wait()를 호출하기 전에 뮤텍스를 잠금 해제한 다음 고루틴이 깨어날 때 다시 잠가야 합니다. 이 프로세스는 어색하게 느껴질 수 있으며 적시에 잠그거나 잠금 해제하는 것을 잊어버리는 등 오류가 발생하기 쉽습니다.
그런데 왜 패턴이 조금 어긋난 것 같나요?
일반적으로 cond.Wait()를 호출하는 고루틴은 다음과 같이 루프에서 일부 공유 상태를 확인해야 합니다.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
sync.Cond에 포함된 잠금은 잠금/잠금 해제 프로세스를 처리하는 데 도움이 되어 코드를 더 깔끔하게 만들고 오류 가능성을 줄입니다. 패턴에 대해서는 곧 자세히 논의하겠습니다.
이전 예를 자세히 살펴보면 소비자에서 일관된 패턴을 발견할 수 있습니다. 조건을 기다리기(.Wait())하기 전에 항상 뮤텍스를 잠그고 조건이 충족된 후에 잠금을 해제합니다.
또한 대기 조건을 루프 내에 래핑합니다. 다시 한 번 복습해 보겠습니다.
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
sync.Cond에서 Wait()를 호출하면 현재 고루틴이 특정 조건이 충족될 때까지 기다리도록 지시합니다.
비하인드 스토리는 다음과 같습니다.
Wait()이 내부적으로 어떻게 작동하는지 살펴보겠습니다.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
간단하지만 4가지 핵심을 짚어볼 수 있습니다.
이러한 잠금/잠금 해제 동작 때문에 sync.Cond.Wait()를 사용할 때 일반적인 실수를 피하기 위해 따라야 할 일반적인 패턴이 있습니다.
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
"루프 없이 c.Wait()를 직접 사용하면 어떨까요?"
게시물 일부 발췌입니다. 전체 게시물은 여기에서 볼 수 있습니다: https://victoriametrics.com/blog/go-sync-cond/
위 내용은 Go sync.Cond, 가장 간과되는 동기화 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!