Mutex API

The Mutex API provides lightweight thread synchronization inside AngelScript. Mutex objects are native engine primitives exposed as mutex_t handles. They support:

  • Exclusive locks (writers)

  • Shared locks (readers)

  • Non-blocking try-lock operations

  • Automatic cleanup on script shutdown

Mutexes are created by create_mutex() and manually released using destroy()


🔑 Creating Mutexes

Create

mutex_t create_mutex();

Creates a new mutex and registers it with the current AngelScript context.

Example:

mutex_t m = create_mutex();

Destroy

void mutex_t::destroy();

Frees the underlying native mutex.

Note: If the script forgets to call destroy(), the PCX engine frees all remaining mutexes when the script context unloads.

Example:

m.destroy();

🔐 Exclusive Lock (Writer Lock)

Use these when only one thread should modify shared state.

Lock (blocking)

void mutex_t::lock();

Blocks until exclusive ownership is acquired.

Try Lock (non-blocking)

bool mutex_t::try_lock();

Returns true if the lock is acquired immediately, false otherwise.

Unlock

void mutex_t::unlock();

Releases the exclusive lock.


📖 Shared Lock (Reader Lock)

Shared locks allow multiple readers, but automatically block if an exclusive writer holds the lock.

Lock Shared (blocking)

void mutex_t::lock_shared();

Blocks until a shared lock is available.

Try Lock Shared (non-blocking)

bool mutex_t::try_lock_shared();

Returns immediately with true on success.

Unlock Shared

void mutex_t::unlock_shared();

Releases the shared lock.


🧩 Usage Example

mutex_t g_mtx = create_mutex();

int sharedValue = 0;

void writer_thread(int callback_id, int data_index)
{
    g_mtx.lock();
    log("[writer] acquired exclusive lock");
    
    sharedValue += 10;
    
    g_mtx.unlock();
    log("[writer] released lock");
}

void reader_thread(int callback_id, int data_index)
{
    if (g_mtx.try_lock_shared())
    {
        log("[reader] acquired shared lock");
        int v = sharedValue;  // safe read
        g_mtx.unlock_shared();
        log("[reader] released shared lock");
    }
    else
    {
        log("[reader] shared lock unavailable");
    }
}

int main()
{
    
    register_callback(writer_thread, 1, 0);
    register_callback(reader_thread, 1, 0);
    return 1;
}

void on_unload()
{
    log("unloaded");
    // Optional: manual cleanup
    g_mtx.destroy();
}

🗑 Automatic Cleanup

Even if a script forgets:

mutex_t m = create_mutex();
// ... never calls m.destroy()

PCX will:

  • Free each mtx_t safely

  • Avoid leaks and invalid handles


Avoid Deadlocks!

  • You must avoid deadlocks

  • If an exception happens and the active thread had a lock, It will cause a deadlock

  • Use the following try & catch logic to avoid it.

mutex_t g_example_mutex;

// A safe function that guarantees the mutex is always unlocked.
void do_work_safe(bool should_throw)
{
    log("Attempting to lock the mutex...");
    g_example_mutex.lock();
    log("Mutex locked.");

    try
    {
        if (should_throw)
        {
            log("An error occurred! Throwing an exception...");
            throw("Error");
        }
    }
    catch
    {
        log("Caught an exception, but the unlock call will still be reached.");
    }

    // This code is now guaranteed to run, preventing a deadlock.
    g_example_mutex.unlock();
    log("Mutex unlocked.");
}

void main()
{
    g_example_mutex = create_mutex();

    // 1. Call the function and make it throw an exception.
    //    The function will catch its own exception and unlock the mutex.
    do_work_safe(true);
    
    // 2. Call it again. This will now succeed because the mutex was released.
    log("Calling the function again. This will work correctly.");
    do_work_safe(false);

    log("Program finished successfully.");
}

Last updated