πŸ”‘MultiLock in Areg SDK: Portable, Efficient Mixed Synchronization in C++



This content originally appeared on DEV Community and was authored by Artak Avetyan

In multithreaded applications, threads rarely wait for just one condition. More often, they need to coordinate across resources (mutexes) and signals (events), sometimes with semaphores or timers thrown into the mix.

Classic challenge: how do you wait for a resource without missing a shutdown signal, all while avoiding busy loops, race conditions, or verbose boilerplate?

The answer: Areg’s MultiLock β€” a portable, mixed-object wait primitive that works on Windows and Linux. It handles Mutex, SynchEvent, Semaphore, and Waitable Timers in one unified API. MultiLock isn’t a luxury β€” it’s a critical tool for efficiency and clarity in real-world multithreading.

Why MultiLock is Not Optional

Without MultiLock, developers face these recurring problems:

  • Racey flags: Threads constantly check booleans or condition variables to see if they should proceed or shutdown.
  • Busy-wait loops: Polling locks repeatedly wastes CPU.
  • Verbose STL workarounds: Combining std::condition_variable + std::unique_lock + std::try_lock for multiple mutexes is error-prone.
  • Platform inconsistencies: Windows has WaitForMultipleObjects, but POSIX lacks a single call to wait on heterogeneous objects.

MultiLock solves all of this elegantly: one call, cross-platform, no polling, predictable behavior.

Real-World Use Cases

Here are practical scenarios where MultiLock shines:

  1. Graceful worker shutdown while holding a mutex

    • A thread waits for a resource (mutex) but must exit immediately if a shutdown event is signaled.
    • MultiLock handles both in one call, avoiding fragile loops.
  2. Interruptible multi-resource acquisition

    • Tasks that need multiple mutexes to operate (e.g., swap buffers or update multiple shared structures).
    • With MultiLock, you can cancel or preempt acquisition if a cancellation event fires.
  3. Service startup with dependency gating

    • Startup requires a config to load (event), a license token (semaphore), and exclusive access to an init resource (mutex).
    • MultiLock waits until all conditions are satisfied, with optional wait-any or wait-all modes.
  4. Thread pool cancellation and resource fairness

    • Multiple threads contend for shared resources. MultiLock allows a global stop signal without losing mutex guarantees or fairness.

Compact Code Example β€” Share Resources and Shutdown Thread

// Mixed objects: two mutexes + shutdown event
IESynchObject* objects[] = { &shutdownEvent, &mutexA, &mutexB };
MultiLock multiLock(objects, MACRO_ARRAYLEN(objects), false); // wait-any

int index = multiLock.lock(NECommon::WAIT_INFINITE, false, false);

if (index == 0 ) {
    // Shutdown event signaled β†’ abort gracefully
} else if (index == 1 || index == 2) {
    // Resource mutex available β†’ proceed with work
}

Full working example (events, mutexes) is in 10_synch demo of areg-sdk repo at GitHub.

STL vs Areg’s MultiLock β€” Complexity Comparison

Even with std::scoped_lock and std::condition_variable, standard C++ cannot wait on a mutex AND a shutdown event in a single call. You must:

  • Use try-lock loops
  • Periodically check a shutdown flag
  • Combine with condition variables
  • Avoid deadlocks manually

Example sketch:

std::mutex mA, mB, shutdownMutex;
std::condition_variable shutdownCv;
bool shutdown = false;

while(true) {
    std::unique_lock<std::mutex> sh_lock(shutdownMutex);
    if(shutdown) break;

    std::scoped_lock lock(mA, mB); // blocks until both mutexes acquired
    // cannot be preempted by shutdown directly β†’ need try_lock loops
}

Why MultiLock wins:

  • One call to wait on multiple heterogeneous objects
  • Immediate reaction to shutdown signals
  • Less code, fewer bugs, higher efficiency

ASCII Diagram β€” How MultiLock Works

Thread
  |
  v
[ MultiLock(objects: ShutdownEvent, MutexA, MutexB) ]
         |
         +---> ShutdownEvent  --> exit thread
         |
         +---> MutexA ready  --> proceed
         |
         +---> MutexB ready  --> proceed

Comparison Table β€” Clear, Verified

API / Framework Mixed multi-wait? Portability Notes
Areg SDK MultiLock ✅ Yes ✅ Cross-platform Mutex, SynchEvent, Semaphore, Waitable Timer β€” unified, efficient API.
Win32 WaitForMultipleObjects ✅ Yes ❌ Windows-only Accepts event, mutex, semaphore, thread, timer handles.
POSIX pthreads ❌ No ❌ Unix-like Must combine condvars + mutex manually.
C++ STL (mutex, condvar) ❌ No ✅ Cross-platform No direct single-call mixed-object wait; must emulate with try_lock + condition_variable.
Boost.Thread / Futures ⚠ Partial ✅ Cross-platform Async composition possible, but no OS-level synchronous mixed-object wait.

✅ Efficiency takeaway: MultiLock is not a luxury. It reduces CPU cycles, avoids polling, prevents racey manual loops, and provides one clean abstraction for a common yet previously cumbersome problem.

Extra Notes

  • Waitable Timers: integrate easily for deadlines (e.g., stop if a timeout occurs) β€” behaves consistently across Windows/Linux.
  • Wait-any vs Wait-all: choose whether you want to wake on the first object or all objects ready.
  • Index-based results: you know precisely which object caused the wake-up.

Call to Action

👉 See MultiLock in a real-world scenario: 10_synch example in Areg Framework

💡If MultiLock looks like the tool you wish you had earlier, show support by starring the repo: ⭐ Star areg-sdk on GitHub

Your star helps grow the community and fuels cleaner, safer, and truly cross-platform C++ concurrency.


This content originally appeared on DEV Community and was authored by Artak Avetyan