Multithreading

There are some utilities for using AngelScript in the multithreading environment.

Note

AngelScript does not support multithreading on all platforms.

inline bool asbind20::has_threads(const char *options = asGetLibraryOptions())

Check if asGetLibraryOptions() doesn’t return "AS_NO_THREADS"

Initializing and Cleaning the Multithreading Environment

According to the official document, AngelScript needs some additional efforts to work in the multithreading environment.

The following functions are provided by the external header concurrent/threading.hpp.

inline void asbind20::concurrent::auto_thread_cleanup() noexcept

Mark this thread needs to clean up AngelScript data before terminating.

It’s safe to call this function for the second time in the same thread.

Note

Remember to call this function in any thread other than main thread to prevent memory leak.

inline void asbind20::concurrent::prepare_multithread(asIThreadManager *external_mgr = nullptr)

Call this function in the main thread to prepare for multithreading.

Warning

Call this function before any script engine is created, and make sure global variables don’t contain any script engine that may be released after asUnprepareMultithread being called.

Example code:

#include <thread>
#include <asbind20/asbind.hpp>
#include <asbind20/concurrent/threading.hpp>

int main()
{
    if(!asbind20::has_threads())
    {
        std::cerr << "AS_NO_THREADS" << std::endl;
        return 1;
    }

    using namespace asbind20;
    concurrent::prepare_multithread();

    auto engine = make_script_engine();

    auto* m = engine->GetModule(
        "script_multithreading", asGM_ALWAYS_CREATE
    );
    m->AddScriptSection(
        "script_multithreading",
        "int fn(int arg) { return arg * 2; }"
    );
    m->Build();
    auto* f = m->GetFunctionByName("fn");

    std::condition_variable cv;
    std::mutex mx;
    int result = -1;

    auto helper = [&, f](int arg)
    {
        concurrent::auto_thread_cleanup();

        {
            using namespace std::chrono_literals;

            request_context ctx(engine);
            std::this_thread::sleep_for(3ms); // Emulates running a complex script
            auto r = script_invoke<int>(ctx, f, arg);
            std::unique_lock lock(mx);
            result = r.value();
        }

        cv.notify_all();
    };

    std::thread thr(helper, 10);
    thr.detach();
    {
        std::unique_lock lock(mx);
        cv.wait(lock);
    }
    assert(result == 20);

    return 0;
}

Locks

The weak reference flag (asILockableSharedBool*) is lockable.

The lockable_shared_bool class (documented in RAII Helpers) wraps the weak reference flag for use with std::lock_guard.

The AngelScript library provides global exclusive and shared locks. The wrappers for them are provided by the header <asbind20/concurrent/mutex.hpp>.

constexpr script_lock_t asbind20::script_lock = {}

Global lock of AngelScript library.

class script_lock_t

Wrapper for global lock of AngelScript library.

Public Static Functions

static inline void lock()

Locks the mutex, blocks if the mutex is not available.

static inline void unlock()

Unlocks the mutex.

static inline void lock_shared()

Locks the mutex for shared ownership, blocks if the mutex is not available.

static inline void unlock_shared()

Unlocks the mutex (shared ownership)

Example code:

{
    std::unique_lock lk(asbind20::script_lock);
    // Writing
}
{
    std::shared_lock lk(asbind20::script_lock);
    // Reading
}

Atomic Reference Counting

class atomic_counter

Atomic counter for multithreading.

This wraps the asAtomicInc and asAtomicDec.

Note

Its initial value will be 1.

Increment and decrement operators

Even the prefix increment / decrement will return int value directly, which is similar to how the std::atomic<T> does.

inline int operator++() noexcept
inline int operator--() noexcept

Public Types

using value_type = int

Public Functions

inline atomic_counter() noexcept

Construct a new atomic counter and set the counter value to 1.

atomic_counter(const atomic_counter&) = default
inline atomic_counter(int val) noexcept
~atomic_counter() = default
atomic_counter &operator=(const atomic_counter&) noexcept = default
inline atomic_counter &operator=(int val) noexcept
bool operator==(const atomic_counter&) const = default
inline int inc() noexcept
inline int dec() noexcept
inline operator int() const noexcept
template<typename Destroyer, typename ...Args>
inline decltype(auto) dec_and_try_destroy(Destroyer &&d, Args&&... args)

Decrease reference count. It will call the destroyer if the count reaches 0.

template<typename T>
inline void dec_and_try_delete(T *ptr) noexcept

Decrease reference count. It will delete the pointer if the count reaches 0.

For garbage collected types, please read the official document about thread safety and GC.