잘못 동작하는 다중 스레드 응용 프로그램의 일반 패턴Common Patterns for Poorly-Behaved Multithreaded Applications

동시성 시각화 도우미를 사용하면 개발자는 다중 스레드 응용 프로그램의 동작을 시각화할 수 있습니다.The Concurrency Visualizer helps developers to visualize the behavior of a multithreaded application. 이 도구에는 잘못 동작하는 다중 스레드 응용 프로그램에 대한 일반적인 패턴의 갤러리가 포함되어 있습니다.This tool includes a gallery of common patterns for multithreaded applications that behave badly. 갤러리에는 각 패턴으로 표시되는 동작에 대한 설명, 해당 동작의 가능한 결과 및 그 동작을 해결하는 가장 일반적인 방법과 함께 도구를 통해 노출되는 일반적이고 인식 가능한 시각적 패턴이 포함되어 있습니다.The gallery includes typical and recognizable visual patterns that are exposed through the tool, together with an explanation of the behavior that is represented by each pattern, the likely result of that behavior, and the most common approach to resolve it.

잠금 경합 및 직렬화된 실행Lock Contention and Serialized Execution

잠금 경합으로 인해 발생하는 직렬화된 실행Lock Contention Resulting in Serialized Execution

스레드가 여러 개 있고 컴퓨터에 논리 코어 수가 충분한 경우에도 때로 병렬화된 응용 프로그램이 직렬로 계속 실행할 수 있습니다.Sometimes a parallelized application stubbornly continues to execute serially even though it has several threads and the computer has a sufficient number of logical cores. 첫 번째 증상은 다중 스레드의 성능 저하로, 직렬 구현보다 약간 더 느릴 수도 있습니다.The first symptom is poor multithreaded performance, perhaps even a bit slower than a serial implementation. 스레드 뷰에서 병렬로 실행 중인 여러 스레드는 볼 수 없지만 대신, 하나의 스레드만 실행되는 것은 언제든지 볼 수 있습니다.In Threads View, you do not see multiple threads running in parallel; instead, you see that only one thread is executing at any time. 여기서 스레드의 동기화 세그먼트를 클릭하면 차단된 스레드(차단 호출 스택) 및 차단 조건을 제거하는 스레드(차단 해제 호출 스택)에 대한 호출 스택을 볼 수 있습니다.At this point, if you click a synchronization segment in a thread, you can see a call stack for the blocked thread (blocking call stack) and the thread that removed the blocking condition (unblocking call stack). 또한 분석 중인 프로세스에서 호출 스택 차단 해제가 발생하는 경우 스레드 준비 커넥터가 표시됩니다.In addition, if the unblocking call stack occurs in the process that you are analyzing, a Thread-Ready connector is displayed. 여기서, 차단 및 차단 해제 호출 스택으로부터 코드로 이동하여 더 많은 serialization의 원인을 조사할 수 있습니다.From this point, you can navigate to your code from the blocking and unblocking call stacks to investigate the cause of serialization even more.

다음 그림과 같이 동시성 시각화 도우미가 CPU 사용률 뷰에 이 증상을 표시할 수 있습니다. 여기서 다중 스레드가 있더라도 응용 프로그램은 하나의 논리 코어만 사용합니다.As shown in the following illustration, the Concurrency Visualizer can also expose this symptom in the CPU Utilization View, where, despite the presence of multiple threads, the application consumes only one logical core.

자세한 내용은 MSDN 블로그 웹 사이트에 있는 Hazim Shafi의 Parallel Performance Tools For Windows(Windows용 병렬 성능 도구) 블로그에서 "Performance Pattern 1: Identifying Lock Contention"(성능 패턴 1: 잠금 경합 식별)을 참조하세요.For more information, see "Performance Pattern 1: Identifying Lock Contention" in Hazim Shafi's Parallel Performance Tools For Windows blog on the MSDN blog Web site.

잠금 경합Lock Contention

균등하지 않은 작업 부하 분산Uneven Workload Distribution

균등하지 않은 작업 부하Uneven Workload

위의 그림처럼 응용 프로그램에서 여러 병렬 스레드 간에 균등하지 않은 작업 분산이 발생하면 각 스레드가 작업을 완료할 때 일반적인 계단 단계 패턴이 나타납니다.When an irregular distribution of work occurs across several parallel threads in an application, a typical stair-step pattern appears as each thread completes its work, as shown in the previous illustration. 동시성 시각화 도우미는 대개 각 동시 스레드에 대해 근접한 시작 시간을 보여줍니다.The Concurrency Visualizer most often shows very close start times for each concurrent thread. 그러나 이러한 스레드는 동시에 종료하는 대신 일반적으로 불균등하게 종료됩니다.However, these threads typically end in an irregular manner instead of ending simultaneously. 이 패턴은 병렬 스레드의 그룹 간에 불균등한 작업 분산을 나타내며, 이로 인해 성능이 저하될 수 있습니다.This pattern indicates an irregular distribution of work among a group of parallel threads, which could lead to decreased performance. 이러한 문제를 해결하기 위해서는 병렬 스레드 간에 작업을 나누는 알고리즘을 다시 평가하는 것이 좋습니다.The best approach to such a problem is to reevaluate the algorithm by which work has been divided among the parallel threads.

다음 그림과 같이 동시성 시각화 도우미는 CPU 사용률 뷰에 이 증상을 CPU 사용률의 점진적인 단계별 감소로 표시할 수 있습니다.As shown in the following illustration, the Concurrency Visualizer can also expose this symptom in the CPU Utilization View as a gradual step-down in CPU utilization.

균등하지 않은 작업 부하Uneven Workload

초과 구독Oversubscription

초과 구독Oversubscription

초과 구독이 발생하는 경우 프로세스의 활성 스레드 수가 시스템에서 사용 가능한 논리 코어의 수보다 큽니다.In the case of oversubscription, the number of active threads in a process is larger than the number of available logical cores on the system. 위의 그림에서는 모든 활성 스레드의 상당한 선점 밴드와 함께 초과 구독의 결과를 보여 줍니다.The previous illustration shows the results of oversubscription, with significant preemption banding in all active threads. 또한 범례를 보면 상당한 시간이 선점에 소요된 것(이 예제의 84%)을 알 수 있습니다.In addition, the legend shows a large percentage of time is spent in Preemption (84 percent in this example). 이는 프로세스에서 논리 코어 수보다 더 많은 동시 스레드를 실행하도록 시스템에 요청하는 것을 나타냅니다.This may indicate that the process is asking the system to execute more concurrent threads than the number of logical cores. 그러나 시스템의 다른 프로세스가 이 프로세스에 사용 가능한 것으로 간주된 리소스를 사용하는 것을 나타낼 수도 있습니다.However, this may also indicate that other processes on the system are using resources that were assumed to be available to this process.

이 문제를 평가할 때 다음 사항을 고려해야 합니다.You should consider the following when you evaluate this problem:

  • 전체 시스템이 초과 구독될 수 있습니다.The overall system may be oversubscribed. 시스템의 다른 프로세스가 스레드를 선점하는 것이 좋습니다.Consider that other processes on the system may be preempting your threads. 스레드 뷰에서, 선점 세그먼트 위에서 마우스를 잠시 멈추면 도구 설명에서 스레드 및 스레드를 선점한 프로세스를 식별해 줍니다.When you pause over a preemption segment in the threads view, a tooltip will identify the thread and the process that preempted the thread. 이 프로세스가 반드시 사용자의 프로세스가 선점된 전체 시간 동안 실행된 프로세스는 아닐 수 있지만 사용자의 프로세스에 대해 선점 압력을 형성한 요인에 대한 힌트를 제공합니다.This process is not necessarily the one that executed during the whole time that your process was preempted, but it provides a hint about what created the preemption pressure against your process.

  • 이 작업의 단계 중 사용자의 프로세스에서 실행할 적절한 스레드 수를 확인하는 방법을 평가합니다.Evaluate how your process determines the appropriate number of threads for execution during this phase of work. 프로세스에서 직접 활성 병렬 스레드의 수를 계산하는 경우 시스템의 사용 가능한 논리 코어 수를 더 적절히 처리하도록 알고리즘을 수정하는 것이 좋습니다.If your process directly calculates the number of active parallel threads, consider modifying that algorithm to better account for the number of available logical cores on the system. 동시성 런타임, 작업 병렬 라이브러리 또는 PLINQ를 사용하면 이러한 라이브러리에서 스레드 수를 계산하는 작업을 수행합니다.If you use the Concurrency Runtime, the Task Parallel Library, or PLINQ, these libraries perform the work of calculating the number of threads.

불충분한 I/OInefficient I/O

불충분한 I/OInefficient I/O

I/O의 오용 또는 남용은 응용 프로그램의 비효율성에 대한 일반적인 원인입니다.Overuse or misuse of I/O is a common cause of inefficiencies in applications. 위의 그림을 살펴보세요.Consider the previous illustration. 표시되는 시간 표시 막대 프로필은 표시되는 스레드 시간의 44%가 I/O에서 사용되는 것을 보여줍니다.The Visible Timeline Profile shows that 44 percent of the visible thread time is consumed by I/O. 타임라인은 많은 양의 I/O를 보여 주며, 이는 프로파일링된 응용 프로그램이 자주 I/O에 의해 차단되었음을 나타냅니다.The timeline shows large amounts of I/O, which indicates that the profiled application is frequently blocked by I/O. I/O의 종류 및 프로그램이 차단되는 위치에 대한 세부 정보를 보려면 문제 영역을 확대하고, 표시되는 시간 표시 막대 프로필을 검사한 다음 특정 I/O 블록을 클릭하여 현재 호출 스택을 표시합니다.To see details about the kinds of I/O and where your program is blocked, zoom into problematic regions, examine the Visible Timeline Profile, and then click a specific I/O block to see current call stacks.

잠금 호송Lock Convoys

잠금 호송Lock Convoys

응용 프로그램이 선착순으로 잠금을 획득하고 잠금의 도착률이 잠금 획득률보다 높은 경우 잠금 호송이 발생합니다.Lock convoys occur when the application acquires locks in a first-come, first-served order, and when the arrival rate at the lock is higher than the acquisition rate. 이러한 두 조건이 결합되면 잠금에서 백업을 시작하라는 요청이 발생합니다.The combination of these two conditions causes requests for the lock to start backing up. 이 문제를 해결하는 방법은 "불공정" 잠금 또는 잠금 해제된 상태의 스레드를 찾을 수 있도록 첫 번째 스레드에 대한 액세스를 제공하는 잠금을 사용하는 것입니다.One way to combat this problem is to use "unfair" locks, or locks that give access to the first thread to find them in unlocked states. 위의 그림에서는 이 호송 동작을 보여 줍니다.The previous illustration shows this convoy behavior. 이 문제를 해결하려면 동기화 개체에 대한 경합을 줄이고 불공정 잠금을 사용해 보세요.To solve the problem, try reducing contention for the synchronization objects and try using unfair locks.

참고 항목See also

스레드 뷰Threads View