# How Multi-Threading Works with Dear ImGui Contexts: 3 Safe Patterns

> Learn how multi-threading works with Dear ImGui contexts. Discover 3 safe patterns including synchronization, data staging, and thread-local storage for efficient rendering.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: internals
- Published: 2026-07-30

---

**Dear ImGui contexts are not thread-safe by default; you must either synchronize every API call with a mutex, stage the resulting `ImDrawData` for a separate render thread, or compile with thread-local storage to maintain independent contexts per thread.**

Dear ImGui (ocornut/imgui) centralizes all per-application state in opaque `ImGuiContext` structures accessed through the global pointer `GImGui`. Because this pointer is a plain global variable defined in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h), concurrent access from multiple threads causes data races on critical members like `ActiveId`, `CurrentWindow`, and `SettingsHandlers`. Understanding how multi-threading works with Dear ImGui contexts requires adopting one of three documented patterns that ensure thread safety without modifying the library core.

## Understanding the Global Context Pointer

The library exposes the current context through `ImGui::GetCurrentContext()` and switches contexts via `ImGui::SetCurrentContext()`, both declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h). Internally, these functions manipulate the global pointer `GImGui` defined near line 227 of [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h):

```cpp
// imgui_internal.h (line 227)
#ifndef GImGui
#define GImGui GImGui
#endif
extern IMGUI_API ImGuiContext* GImGui;  // Current implicit context pointer

```

All inline helper functions—such as `ImGui::GetIO()`, `ImGui::GetCurrentWindow()`, and the entirety of `NewFrame()`—dereference this pointer without synchronization. Consequently, a single `ImGuiContext*` must never be accessed simultaneously from multiple threads.

## Three Safe Patterns for Multi-Threading

The official FAQ and reference implementations support three distinct strategies for using Dear ImGui in concurrent environments.

### 1. Mutex-Protected Single Context (Lock-Around-the-Context)

Wrap every ImGui call that touches the same `ImGuiContext*` with a user-provided `std::mutex`. This serializes all UI updates and rendering, preventing simultaneous read/write access to the context's internal state.

Use this pattern for simple debug tools or background updates where UI work is brief and the main thread can tolerate blocking. Declare the mutex alongside your context pointer:

```cpp
std::mutex g_ImGuiMutex;
ImGuiContext* g_ImGuiCtx = nullptr;  // Created once via ImGui::CreateContext()

```

### 2. Staging ImDrawData for Render Threads

Build the UI on a producer thread (typically the update loop) and hand the resulting `ImDrawData` to a consumer thread that executes GPU draw calls. The `ImDrawDataSnapshot` helper struct (available in the **imgui_threaded_rendering** example from the ImGui Club repository) creates a thread-safe copy of vertex/index buffers and texture references, allowing the render thread to operate without touching the live context.

This approach fits classic game-loop architectures where the UI updates on the logic thread but submits to a dedicated graphics thread. The snapshot is created after `ImGui::Render()` and passed across threads via a lock-free queue.

### 3. Thread-Local Storage with One Context Per Thread

Define `GImGui` as a thread-local variable so each OS thread maintains an independent context pointer. Add the following to [`imconfig.h`](https://github.com/ocornut/imgui/blob/main/imconfig.h) or your build configuration before including Dear ImGui headers:

```cpp
// For GCC/Clang
#define GImGui __thread ImGuiContext* GImGui

// For MSVC
// #define GImGui __declspec(thread) ImGuiContext* GImGui

```

Each thread then calls `ImGui::CreateContext()` to obtain a unique instance, enabling parallel UI rendering for multiple viewports or isolated editor panes without cross-thread synchronization.

## Core API Functions and Source Files

The public API for context management resides in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) and [`imconfig.h`](https://github.com/ocornut/imgui/blob/main/imconfig.h) (for the TLS override):

```cpp
// imgui.h
IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL);
IMGUI_API void          DestroyContext(ImGuiContext* ctx = NULL);
IMGUI_API ImGuiContext* GetCurrentContext();
IMGUI_API void          SetCurrentContext(ImGuiContext* ctx);

```

These functions allocate the `ImGuiContext` structure (containing `Windows`, `InputTextState`, `Style`, and runtime variables) and bind it to the current thread's `GImGui` pointer. When using the thread-local pattern, `SetCurrentContext()` updates only the calling thread's instance, while other threads retain their own isolated contexts.

## Practical Implementation Examples

### Synchronizing with a Mutex

```cpp
// ui_thread.cpp
#include "imgui.h"
#include <mutex>
#include <thread>

extern std::mutex g_ImGuiMutex;
extern ImGuiContext* g_ImGuiCtx;

void UIUpdateThread()
{
    std::lock_guard<std::mutex> lock(g_ImGuiMutex);
    ImGui::SetCurrentContext(g_ImGuiCtx);
    ImGui::NewFrame();
    
    ImGui::Begin("Background Tool");
    ImGui::Text("Thread ID: %d", std::this_thread::get_id());
    ImGui::End();
    
    ImGui::Render();
    // Store ImGui::GetDrawData() for the render thread
}

```

### Staging Draw Data for Async Rendering

```cpp
// Requires imgui_threaded_rendering.h from imgui_club
#include "imgui_threaded_rendering.h"
#include <queue>
#include <mutex>

std::mutex g_QueueMutex;
std::queue<ImDrawDataSnapshot> g_DrawQueue;

void ProducerThread()
{
    ImGui::SetCurrentContext(g_ImGuiCtx);
    ImGui::NewFrame();
    // ... populate UI ...
    ImGui::Render();
    
    std::lock_guard<std::mutex> lock(g_QueueMutex);
    g_DrawQueue.emplace(*ImGui::GetDrawData());
}

void ConsumerRenderThread()
{
    ImDrawDataSnapshot snapshot;
    {
        std::lock_guard<std::mutex> lock(g_QueueMutex);
        if (g_DrawQueue.empty()) return;
        snapshot = std::move(g_DrawQueue.front());
        g_DrawQueue.pop();
    }
    // Submit snapshot.VertexBuffer and IndexBuffer to GPU
    // No ImGui context accessed here
}

```

### Per-Thread Contexts with TLS

```cpp
// Define before any Dear ImGui includes
#define GImGui __thread ImGuiContext* GImGui
#include "imgui.h"

void WorkerThread()
{
    ImGuiContext* ctx = ImGui::CreateContext();
    ImGui::SetCurrentContext(ctx);
    
    ImGui::NewFrame();
    ImGui::Begin("Isolated Window");
    ImGui::Text("Private context on thread %d", std::this_thread::get_id());
    ImGui::End();
    ImGui::Render();
    
    // Render or transfer ImDrawData...
    ImGui::DestroyContext(ctx);
}

```

## Summary

- A single `ImGuiContext` is **not thread-safe**; concurrent access causes data races on internal state members.
- **Three safe patterns** exist: mutex-wrapped access for simple serialization, `ImDrawDataSnapshot` for producer-consumer architectures, and thread-local `GImGui` for parallel independent contexts.
- The global pointer `GImGui` lives in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) (line 227) and defaults to a plain global, but can be redefined as thread-local via [`imconfig.h`](https://github.com/ocornut/imgui/blob/main/imconfig.h).
- Always use `ImGui::CreateContext()` and `ImGui::SetCurrentContext()` to manage context lifecycles, and consult the **imgui_threaded_rendering** example for production-ready draw data staging.

## Frequently Asked Questions

### Can I call ImGui::NewFrame() from one thread and ImGui::Render() from another?

No, you cannot call `ImGui::NewFrame()` and `ImGui::Render()` on different threads for the same context without synchronization. These functions both read and modify shared state within the `ImGuiContext` structure. You must either wrap both calls with the same mutex or use the staging pattern where the producer thread completes `Render()`, captures the `ImDrawData`, and passes it to the consumer thread.

### How do I safely pass ImDrawData to a render thread without copying?

Use the `ImDrawDataSnapshot` helper from the **imgui_threaded_rendering** example in the ImGui Club repository. This struct performs a deep copy of the vertex and index buffers while maintaining texture references, allowing the render thread to consume the data without accessing the live ImGui context. The original context can then begin the next frame immediately while the GPU processes the snapshot.

### What is the performance cost of using thread-local storage for GImGui?

The cost is negligible on modern compilers and operating systems—typically a single pointer indirection per ImGui call, identical to the global version. The primary trade-off is memory usage, as each thread maintains a full `ImGuiContext` instance (including font atlases and window buffers). Use this pattern only when you genuinely need concurrent UI processing across multiple threads, not for simple background loading screens.

### Where should I define GImGui as thread-local?

Define `GImGui` as a thread-local variable in [`imconfig.h`](https://github.com/ocornut/imgui/blob/main/imconfig.h) (which is included by [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)) or pass it as a compiler flag (`-DGImGui=__thread\ ImGuiContext*\ GImGui`). This ensures the definition precedes the declaration in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h). Avoid modifying [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) directly to preserve compatibility with upstream updates.