C#
lock()
Managed C++
multithreading
synchronization

C's lock in Managed C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

C# has a lock statement, but C++/CLI and older managed C++ code do not use the same language keyword. The equivalent behavior is implemented with System::Threading::Monitor, usually by calling Monitor::Enter and Monitor::Exit inside a try and finally pattern so the lock is always released.

What C# lock Really Expands To

In C#, this:

csharp
1private readonly object _gate = new object();
2
3lock (_gate)
4{
5    counter++;
6}

is conceptually similar to:

csharp
1bool taken = false;
2try
3{
4    System.Threading.Monitor.Enter(_gate, ref taken);
5    counter++;
6}
7finally
8{
9    if (taken)
10    {
11        System.Threading.Monitor.Exit(_gate);
12    }
13}

That matters because in managed C++ or C++/CLI you generally write the second form yourself.

Equivalent Pattern in C++/CLI

The direct translation uses System::Object^ as the lock object and Monitor to guard the critical section.

cpp
1using namespace System;
2using namespace System::Threading;
3
4ref class Counter
5{
6private:
7    Object^ gate;
8    int value;
9
10public:
11    Counter() : gate(gcnew Object()), value(0) {}
12
13    void Increment()
14    {
15        bool taken = false;
16        try
17        {
18            Monitor::Enter(gate, taken);
19            value++;
20        }
21        finally
22        {
23            if (taken)
24            {
25                Monitor::Exit(gate);
26            }
27        }
28    }
29
30    int GetValue()
31    {
32        return value;
33    }
34};

The important property is not the syntax. It is the guarantee that only one thread at a time enters the protected region for the same lock object.

Choose a Private Lock Object

Do not lock on objects that outside code can also lock. In both C# and C++/CLI, the safest pattern is a private lock dedicated to one synchronization purpose.

Good choices:

  • a private Object^
  • a private readonly object in C#

Bad choices:

  • 'this'
  • public objects
  • string literals
  • type objects shared too widely

If unrelated code can lock the same object, you create accidental contention or deadlock risk.

Keep the Locked Region Small

A lock protects correctness, but it also serializes access. Keep the critical section focused on the shared mutable state that actually needs protection.

cpp
1void IncrementSafely()
2{
3    bool taken = false;
4    try
5    {
6        Monitor::Enter(gate, taken);
7        value++;
8    }
9    finally
10    {
11        if (taken)
12        {
13            Monitor::Exit(gate);
14        }
15    }
16
17    // Do slower non-critical work outside the lock.
18}

This reduces contention and makes it easier to reason about what the lock is really protecting.

Use the Right Primitive for the Problem

Monitor is excellent for mutual exclusion inside one process. It is not the right tool for every concurrency problem. Depending on the case, you may want:

  • 'Interlocked for simple counters'
  • 'ReaderWriterLockSlim for read-heavy structures'
  • 'SemaphoreSlim for limited concurrency'
  • concurrent collections instead of manual locking

Still, if you specifically want the C# lock equivalent in managed C++, Monitor is the direct answer.

Remember Reentrancy and Exception Safety

Monitor is reentrant for the same thread, which means a thread that already owns the lock can enter it again. That behavior matches C# lock, but it does not remove the need for careful structure. If different methods lock in inconsistent orders across several objects, deadlocks are still possible.

The bigger day-to-day issue is exception safety. If code throws after Monitor::Enter and before Monitor::Exit, the lock must still be released. That is why the try and finally structure is not optional ceremony. It is the thing that keeps one transient exception from freezing every other thread that later needs the same guarded resource.

Common Pitfalls

  • Calling Monitor::Enter without guaranteeing Monitor::Exit in a finally path.
  • Locking on this or another object visible outside the class.
  • Holding the lock while doing slow I/O or unrelated computation.
  • Using Monitor when a simpler atomic primitive such as Interlocked would be enough.
  • Assuming the syntax difference means the threading semantics are different from C# lock.

Summary

  • C# lock maps conceptually to Monitor::Enter and Monitor::Exit.
  • In C++/CLI, write the try and finally pattern explicitly.
  • Lock on a private object dedicated to synchronization.
  • Keep the critical section narrow so contention stays low.
  • Use Monitor for mutual exclusion inside a process, not as a universal concurrency tool.

Course illustration
Course illustration

All Rights Reserved.