Skip to content

Commit 8a8b277

Browse files
committed
Add C++ sample to article on debugging a deadlock
1 parent ce0fbd1 commit 8a8b277

File tree

4 files changed

+126
-40
lines changed

4 files changed

+126
-40
lines changed

docs/debugger/get-started-debugging-multithreaded-apps.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ In the **Parallel Stacks** window, you can switch between a Threads view and (fo
268268
- A new thread (on the right) is also starting but is stopped on `ThreadHelper.ThreadStart`.
269269
::: moniker-end
270270

271+
> [!NOTE]
272+
> For more information on using the **Threads** view, see [Debug a deadlock using the Threads view](../debugger/how-to-use-the-threads-window.md).
273+
271274
2. To view the threads in a list view, select **Debug** > **Windows** > **Threads**.
272275

273276
::: moniker range=">= vs-2022"
@@ -276,9 +279,6 @@ In the **Parallel Stacks** window, you can switch between a Threads view and (fo
276279
In this view, you can easily see that thread 20272 is the Main thread and is currently located in external code, specifically *System.Console.dll*.
277280
::: moniker-end
278281

279-
> [!NOTE]
280-
> For more information on using the **Threads** window, see [Walkthrough: Debug a Multithreaded Application](../debugger/how-to-use-the-threads-window.md).
281-
282282
3. Right-click entries in the **Parallel Stacks** or **Threads** window to see the available options on the shortcut menu.
283283

284284
You can take various actions from these right-click menus. For this tutorial, you explore more of these details in the **Parallel Watch** window (next sections).

docs/debugger/how-to-use-the-threads-window.md

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Debug a deadlock using the Threads view
33
description: Debug a deadlock in a multithreaded application by using the Threads view of the Parallel Stacks window in the Visual Studio integrated development environment (IDE).
4-
ms.date: 7/25/2025
4+
ms.date: 8/19/2025
55
ms.topic: how-to
66
dev_langs:
77
- CSharp
@@ -21,17 +21,25 @@ monikerRange: '>= vs-2022'
2121
---
2222
# Debug a deadlock using the Threads view
2323

24-
This tutorial shows how to use the **Threads** view of **Parallel Stacks** windows to debug a C# multithreaded application. This window helps you understand and verify the run-time behavior of multithreaded code.
24+
This tutorial shows how to use the **Threads** view of **Parallel Stacks** windows to debug a multithreaded application. This window helps you understand and verify the run-time behavior of multithreaded code.
2525

26-
The Threads view is also supported for C++ and Visual Basic, so the same principles described in this article for C# also apply to C++ and Visual Basic.
26+
The Threads view is supported for C#, C++, and Visual Basic. Sample code is provided for C# and C++, but some of the content and illustrations apply only to the C# sample code.principles.
2727

2828
The Threads view helps you to:
2929

3030
- View call stack visualizations for multiple threads, which provides a more complete picture of your app state than the Call Stack window, which just shows the call stack for the current thread.
3131

3232
- Help identify issues such as blocked or deadlocked threads.
3333

34-
## C# sample
34+
## Multithreaded call stacks
35+
36+
Identical sections of the call stack are grouped together to simplify the visualization for complex apps.
37+
38+
The following conceptual animation shows how grouping is applied to call stacks. Only identical segments of a call stack are grouped.
39+
40+
![Illustration of the grouping of call stacks.](../debugger/media/vs-2022/debug-multithreaded-call-stacks.gif)
41+
42+
## Sample code overview (C#, C++)
3543

3644
The sample code in this walkthrough is for an application that simulates a day in the life of a gorilla. The purpose of the exercise is to understand how to use the Threads view of the Parallel Stacks window to debug a multithreaded application.
3745

@@ -46,14 +54,6 @@ To make the call stack intuitive, the sample app performs the following sequenti
4654
1. Gorilla eats.
4755
1. Gorilla engages in monkey business.
4856

49-
## Multithreaded call stacks
50-
51-
Identical sections of the call stack are grouped together to simplify the visualization for complex apps.
52-
53-
The following conceptual animation shows how grouping is applied to call stacks. Only identical segments of a call stack are grouped.
54-
55-
![Illustration of the grouping of call stacks.](../debugger/media/vs-2022/debug-multithreaded-call-stacks.gif)
56-
5757
## Create the sample project
5858

5959
To create the project:
@@ -64,23 +64,24 @@ To create the project:
6464

6565
On the Start window, choose **New project**.
6666

67-
On the **Create a new project** window, enter or type *console* in the search box. Next, choose **C#** from the Language list, and then choose **Windows** from the Platform list.
67+
On the **Create a new project** window, enter or type *console* in the search box. Next, choose **C#** or **C++** from the Language list, and then choose **Windows** from the Platform list.
6868

69-
After you apply the language and platform filters, choose the **Console App** for .NET, and then choose **Next**.
69+
After you apply the language and platform filters, choose the **Console App** for your chosen language, and then choose **Next**.
7070

7171
> [!NOTE]
7272
> If you don't see the correct template, go to **Tools** > **Get Tools and Features...**, which opens the Visual Studio Installer. Choose the **.NET desktop development** workload, then choose **Modify**.
7373
7474
In the **Configure your new project** window, type a name or use the default name in the **Project name** box. Then, choose **Next**.
7575

76-
For .NET, choose either the recommended target framework or .NET 8, and then choose **Create**.
76+
For a .NET project, choose either the recommended target framework or .NET 8, and then choose **Create**.
7777

7878
A new console project appears. After the project has been created, a source file appears.
7979

80-
1. Open the *.cs* code file in the project. Delete its contents to create an empty code file.
80+
1. Open the *.cs* (or *.cpp*) code file in the project. Delete its contents to create an empty code file.
8181

8282
1. Paste the following code for your chosen language into the empty code file.
8383

84+
### [C#](#tab/csharp)
8485
```csharp
8586
using System.Diagnostics;
8687

@@ -191,6 +192,84 @@ To create the project:
191192
}
192193
}
193194
```
195+
196+
### [C++](#tab/cpp)
197+
```cpp
198+
#include <iostream>
199+
#include <thread>
200+
#include <mutex>
201+
#include <vector>
202+
#include <chrono>
203+
204+
namespace Jungle {
205+
206+
std::mutex tree;
207+
std::mutex banana_bunch;
208+
209+
void FindBananas() {
210+
// Lock tree first, then banana
211+
std::lock_guard<std::mutex> lock1(tree);
212+
std::lock_guard<std::mutex> lock2(banana_bunch);
213+
std::cout << "Got bananas." << std::endl;
214+
}
215+
216+
class Gorilla {
217+
bool lockTreeFirst;
218+
public:
219+
Gorilla(bool lockTreeFirst_) : lockTreeFirst(lockTreeFirst_) {}
220+
221+
void WakeUp() {
222+
MorningWalk();
223+
}
224+
225+
void MorningWalk() {
226+
__debugbreak();
227+
if (lockTreeFirst) {
228+
std::unique_lock<std::mutex> lock1(tree);
229+
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulate barrier
230+
FindBananas();
231+
GobbleUpBananas();
232+
}
233+
else {
234+
std::unique_lock<std::mutex> lock2(banana_bunch);
235+
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulate barrier
236+
FindBananas();
237+
GobbleUpBananas();
238+
}
239+
}
240+
241+
void GobbleUpBananas() {
242+
std::cout << "Trying to gobble up food..." << std::endl;
243+
DoSomeMonkeyBusiness();
244+
}
245+
246+
void DoSomeMonkeyBusiness() {
247+
std::this_thread::sleep_for(std::chrono::seconds(1));
248+
std::cout << "Monkey business done" << std::endl;
249+
}
250+
};
251+
252+
void Gorilla_Start(bool lockTreeFirst) {
253+
__debugbreak();
254+
Gorilla koko(lockTreeFirst);
255+
koko.WakeUp();
256+
}
257+
258+
} // namespace Multithreaded_Deadlock
259+
260+
int main() {
261+
using namespace Jungle;
262+
std::vector<std::thread> threads;
263+
threads.emplace_back(Gorilla_Start, true); // First gorilla locks tree then banana
264+
threads.emplace_back(Gorilla_Start, false); // Second gorilla locks banana then tree
265+
266+
for (auto& t : threads) {
267+
t.join();
268+
}
269+
return 0;
270+
}
271+
```
272+
194273
195274
After you update the code file, save your changes and build the solution.
196275
@@ -204,6 +283,9 @@ To start debugging:
204283
205284
1. On the **Debug** menu, select **Start Debugging** (or **F5**) and wait for the first `Debugger.Break()` to be hit.
206285
286+
> [!NOTE]
287+
> In C++, the debugger pauses in `__debug_break()`. The rest of the code references and illustrations in this article are for the C# version, but the same debugging principles apply to C++.
288+
207289
1. Press **F5** once, and the debugger pauses again on the same `Debugger.Break()` line.
208290
209291
This pauses in the second call to `Gorilla_Start`, which occurs within a second thread.
@@ -251,6 +333,9 @@ To start debugging:
251333
252334
The delay is caused by a deadlock. Nothing appears in the Threads view because even though threads may be blocked you aren't currently paused in the debugger.
253335
336+
> [!NOTE]
337+
> In C++, you also see a debug error indicating that `abort()` has been called.
338+
254339
> [!TIP]
255340
> The **Break All** button is a good way to get call stack information if a deadlock occurs or all threads are currently blocked.
256341
@@ -260,6 +345,9 @@ To start debugging:
260345
261346
The top of the call stack in the Threads view shows that `FindBananas` is deadlocked. The execution pointer in `FindBananas` is a curled green arrow, indicating the current debugger context but also it tells us that the threads are not currently running.
262347
348+
> [!NOTE]
349+
> In C++, you don't see the helpful "deadlock detected" information and icons. However, you still find the curled green arrow in `Jungle.FindBananas`, hinting at the location of the deadlock.
350+
263351
In the code editor, we find the curled green arrow in the `lock` function. The two threads are blocked on the `lock` function in the `FindBananas` method.
264352
265353
:::image type="content" source="../debugger/media/vs-2022/debug-multithreaded-parallel-stacks-break-all-editor.png" border="false" alt-text="Screenshot of code editor after selecting Break All." lightbox="../debugger/media/vs-2022/debug-multithreaded-parallel-stacks-break-all-editor.png":::

docs/debugger/using-the-parallel-stacks-window.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,13 @@ The following table describes the main features of the **Threads** view:
194194
::: moniker-end
195195

196196
## Tasks view
197-
If your app uses <xref:System.Threading.Tasks.Task?displayProperty=fullName> objects (managed code) or `task_handle` objects (native code) to express parallelism, you can use **Tasks** view. **Tasks** view shows call stacks of tasks instead of threads.
198197

199-
In **Tasks** view:
198+
For .NET apps using the async/await pattern, the Tasks view is the most helpful for debugging. For a step-by-step tutorial, see [Debug an async application](../debugger/walkthrough-debugging-a-parallel-application.md).
200199

201-
- Call stacks of threads that aren't running tasks aren't shown.
202-
- Call stacks of threads that are running tasks are visually trimmed at the top and bottom, to show the most relevant frames for tasks.
203-
- When several tasks are on one thread, the call stacks of those tasks are shown in separate nodes.
200+
In **Tasks** view, you can:
204201

205-
To see an entire call stack, switch back to **Threads** view by right-clicking in a stack frame and selecting **Go to Thread**.
202+
- View call stack visualizations for apps that use the async/await pattern.
203+
- Identify async code that is scheduled to run but isn't yet running.
206204

207205
The following illustration shows the **Threads** view at the top and the corresponding **Tasks** view at the bottom.
208206

docs/debugger/walkthrough-debugging-a-parallel-application.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,6 @@ The Tasks view helps you to:
3131

3232
- Help identify issues such as the sync-over-async pattern along with hints related to potential issues such as blocked or waiting tasks. The [sync-over-async code pattern](https://devblogs.microsoft.com/pfxteam/should-i-expose-synchronous-wrappers-for-asynchronous-methods/) refers to code that is calling asynchronous methods in a synchronous fashion, which is known to block threads and is the most common cause of thread pool starvation.
3333

34-
## C# sample
35-
36-
The sample code in this walkthrough is for an application that simulates a day in the life of a gorilla. The purpose of the exercise is to understand how to use the Tasks view of the Parallel Stacks window to debug an async application.
37-
38-
The sample includes an example of using the sync-over-async antipattern, which can result in thread pool starvation.
39-
40-
To make the call stack intuitive, the sample app performs the following sequential steps:
41-
42-
1. Creates an object representing a gorilla.
43-
1. Gorilla wakes up.
44-
1. Gorilla goes on a morning walk.
45-
1. Gorilla finds bananas in the jungle.
46-
1. Gorilla eats.
47-
1. Gorilla engages in monkey business.
48-
4934
## Async call stacks
5035

5136
The Tasks view in Parallel Stacks provides a visualization for async call stacks, so you can see what's happening (or supposed to happen) in your application.
@@ -66,6 +51,21 @@ Here are a few important points to remember when interpreting data in the Tasks
6651

6752
![Illustration of the grouping of virtual call stacks.](../debugger/media/vs-2022/debug-asynchronous-virtual-call-stacks-top-bottom.gif)
6853

54+
## C# sample
55+
56+
The sample code in this walkthrough is for an application that simulates a day in the life of a gorilla. The purpose of the exercise is to understand how to use the Tasks view of the Parallel Stacks window to debug an async application.
57+
58+
The sample includes an example of using the sync-over-async antipattern, which can result in thread pool starvation.
59+
60+
To make the call stack intuitive, the sample app performs the following sequential steps:
61+
62+
1. Creates an object representing a gorilla.
63+
1. Gorilla wakes up.
64+
1. Gorilla goes on a morning walk.
65+
1. Gorilla finds bananas in the jungle.
66+
1. Gorilla eats.
67+
1. Gorilla engages in monkey business.
68+
6969
## Create the sample project
7070

7171
1. Open Visual Studio and create a new project.

0 commit comments

Comments
 (0)