스레드를 4개 실행했으므로 스레드 3개는 뮤텍스 잠금을 위해 기다린 것으로 시간 낭비를 했고, 스레드 1개는 Sleep으로 시간을 좀먹고 있었습니다.
➌을 클릭하고 빨간색(Synchronization)이나 회색(Sleep)을 클릭하면 어떤 스레드가 무엇 때문에 잠금이 발생하고 잠을 자는지 등을 확인할 수 있습니다. Concurrency Visualizer를 이용해서 이렇게 성능의 병목을 쉽게 찾아낼 수 있습니다.
다음으로 병렬 병목이 발생하는 흔한 경우를 알아봅시다. 먼저 디바이스 타임(device time)과 CPU 타임을 알아봅시다. 디바이스 타임은 기기에 있는 장치(네트워크 인터페이스, 디스크 등)에 뭔가를 요청해서 결과가 올 때까지 기다리는 시간을 의미합니다. 이때 스레드는 잠자는 상태입니다. 앞서 Sleep(1);을 추가한 이유도 의도적으로 디바이스 타임을 만들기 위해서였습니다.
디바이스 타임 동안에는 CPU가 연산을 하지 않으며 자기 때문에 시간이 낭비됩니다. 따라서 이때는 다른 스레드를 위한 CPU 연산을 하는 것이 효율적입니다. 다음 코드를 볼까요?
lock(A) ReadFromDisk(X) unlock(A)
A가 다른 스레드에서도 자주 잠금을 하는 뮤텍스라면 어떻게 될까요? ReadFromDisk()는 디바이스 타임입니다. 따라서 시리얼 병목이 발생합니다. 그것도 디바이스 타임으로 말이죠. 게다가 디스크에서 읽어 들이는 것이라면, 아마 100만분의 1초 정도 될 것입니다. 그러면 1초에 명령어를 수십억 개 실행할 수 있는 CPU에서 매우 긴 소중한 시간을 낭비하게 됩니다.