답변:
기본적으로 스레드 경합은 한 스레드가 현재 다른 스레드가 보유하고있는 잠금 / 오브젝트를 기다리는 상태입니다. 따라서이 대기 스레드는 다른 스레드가 특정 개체의 잠금을 해제 할 때까지 해당 개체를 사용할 수 없습니다.
몇 가지 답변이 잠금 경합에 초점을 맞추는 것처럼 보이지만 잠금이 경합을 경험할 수있는 유일한 리소스는 아닙니다. 경합은 단순히 두 스레드가 동일한 리소스 또는 관련 리소스에 액세스하려고 할 때 경합 스레드 중 하나 이상이 다른 스레드가 실행 중이 아닐 때보 다 느리게 실행되는 경우입니다.
경합의 가장 확실한 예는 잠금입니다. 스레드 A에 잠금이 있고 스레드 B가 동일한 잠금을 얻으려면 스레드 B가 스레드 A가 잠금을 해제 할 때까지 기다려야합니다.
이제 이것은 플랫폼에 따라 다르지만 다른 스레드가 잠금을 해제 할 때까지 기다릴 필요가 없더라도 스레드가 느려질 수 있습니다! 이는 잠금이 어떤 종류의 데이터를 보호하고 데이터 자체도 종종 경합되기 때문입니다.
예를 들어, 잠금을 획득하고 객체를 수정 한 다음 잠금을 해제하고 다른 작업을 수행하는 스레드를 생각해보십시오. 두 스레드가이 작업을 수행하는 경우 잠금을 위해 싸우지 않더라도 스레드는 하나의 스레드 만 실행 중일 때보 다 훨씬 느리게 실행될 수 있습니다.
왜? 각 스레드가 최신 x86 CPU의 자체 코어에서 실행 중이고 코어가 L2 캐시를 공유하지 않는다고 가정합니다. 스레드가 하나만 있으면 객체가 대부분 L2 캐시에 남아있을 수 있습니다. 두 스레드가 모두 실행되면 한 스레드가 객체를 수정할 때마다 다른 스레드는 다른 CPU가 캐시 라인을 무효화했기 때문에 데이터가 L2 캐시에 없다는 것을 알게됩니다. 예를 들어 Pentium D에서는 코드가 L2 캐시 속도보다 훨씬 낮은 FSB 속도로 실행됩니다.
잠금 자체가 경합되지 않더라도 경합이 발생할 수 있으므로 잠금이 없을 때 경합이 발생할 수도 있습니다. 예를 들어 CPU가 32 비트 변수의 원자 적 증분을 지원한다고 가정 해 보겠습니다. 한 스레드가 변수를 계속 증가 및 감소 시키면 해당 변수는 대부분 캐시에서 뜨겁습니다. 두 개의 스레드가이를 수행하면 해당 캐시는 해당 변수를 보유한 메모리의 소유권을두고 경쟁하게되며 캐시 일관성 프로토콜이 캐시 라인의 각 코어 소유권을 보호하기 위해 작동하므로 많은 액세스가 느려집니다.
아이러니하게도 잠금은 일반적으로 경합을 줄 입니다. 왜? 잠금이 없으면 두 개의 스레드가 동일한 개체 또는 컬렉션에서 작동 할 수 있으며 많은 경합이 발생할 수 있습니다 (예 : 잠금 해제 대기열이 있음). 잠금은 경합 스레드의 일정을 해제하여 경합이 아닌 스레드가 대신 실행되도록합니다. 스레드 A가 잠금을 보유하고 스레드 B가 동일한 잠금을 원하는 경우 구현은 대신 스레드 C를 실행할 수 있습니다. 스레드 C에 해당 잠금이 필요하지 않으면 스레드 A와 B 간의 향후 경합을 잠시 피할 수 있습니다. (물론, 이것은 실행할 수있는 다른 스레드가 있다고 가정합니다. 시스템 전체가 유용한 진전을 이룰 수있는 유일한 방법이 경합하는 스레드를 실행하는 것이라면 도움이되지 않습니다.)
에서 여기 :
경합은 스레드가 쉽게 사용할 수없는 리소스를 기다리고있을 때 발생합니다. 코드 실행 속도가 느려지지만 시간이 지남에 따라 정리 될 수 있습니다.
교착 상태는 스레드가 두 번째 스레드가 잠근 리소스를 기다리고 있고 두 번째 스레드가 첫 번째 스레드가 잠근 리소스를 기다리고있을 때 발생합니다. 두 개 이상의 스레드가 교착 상태에 포함될 수 있습니다. 교착 상태는 스스로 해결되지 않습니다. 종종 전체 응용 프로그램 또는 교착 상태가 발생한 부분이 중지됩니다.
질문의 배경에 대해 OP에서 약간의 설명이 있어야한다고 생각합니다. 2 개의 답변을 생각할 수 있습니다 (이 목록에 추가 사항이 있다고 확신합니다).
스레드 경합의 일반적인 "개념"과 그것이 응용 프로그램에서 어떻게 나타날 수 있는지를 언급하는 경우 위의 @DavidSchwartz의 자세한 답변을 따릅니다.
'.NET CLR Locks and Threads : Total # of Contentions'성능 카운터도 있습니다. 이 카운터에 대한 PerfMon 설명에서 가져온 것처럼 다음과 같이 정의됩니다.
이 카운터는 CLR의 스레드가 성공적으로 관리 잠금을 획득하려고 시도한 총 횟수를 표시합니다. 관리 잠금은 여러 방법으로 획득 할 수 있습니다. C #에서 "lock"문을 사용하거나 System.Monitor.Enter를 호출하거나 MethodImplOptions.Synchronized 사용자 지정 특성을 사용합니다.
... 다른 OS 및 응용 프로그램 프레임 워크를위한 다른 사람들도 확신합니다.
다음 시나리오를 상상해보십시오. 내일 최종 시험을 준비하고 있는데 배가 고프다. 그래서 동생에게 10 달러를주고 피자를 사달라고 부탁합니다. 이 경우, 당신은 메인 스레드이고 당신의 형제는 자식 스레드입니다. 주문이 접수되면 귀하와 귀하의 형제는 동시에 일을하고 있습니다 (예 : 피자 공부와 구매). 이제 고려해야 할 두 가지 사례가 있습니다. 첫째, 동생이 피자를 가져 와서 공부하는 동안 끝납니다. 이 경우 공부를 그만두고 피자를 즐길 수 있습니다. 둘째, 공부를 일찍 끝내고 피자를 먹을 수 있기 전에 잠을 잔다 (즉, 오늘 할당 된 직업-내일의 기말 시험 공부-완료). 물론, 당신은 잠을 잘 수 없습니다. 그렇지 않으면 피자를 먹을 기회가 없습니다.
예에서와 같이 두 경우는 경쟁의 의미를 제공합니다.
잠금 경합은 스레드가 다른 스레드 *에서 이미 획득 한 객체에 대한 잠금을 획득하려고 할 때 발생합니다. 객체가 해제 될 때까지 스레드는 차단됩니다 (즉, 대기 상태에 있음). 경우에 따라 이것은 애플리케이션에 부정적인 영향을 미치는 소위 직렬 실행으로 이어질 수 있습니다.
에서 dotTrace 문서