Skip to content

Threading

IllidanS4 edited this page Jul 12, 2018 · 4 revisions

A thread is an object that allows parallel execution of a specified piece of code. In standard programming languages, a thread is constructed by specifying the code which should be run, and provides methods to control or observe the exceution.

However, since Pawn and AMX weren't designed with the support for multi-threaded programs in mind, the support for parallel execution is still limited in this plugin. First of all, you cannot create new threads directly, and only one piece of code can be running at a time on every AMX machine. To circumvent this limitation, AMX forking can be used.

Running threaded code

Beause of the limitations, instead of specifying the function containing the code that should be run concurrently, the execution of the code is "transferred" to a new thread, which will then run it in parallel with the server's main thread.

print("begin");
threaded(sync_explicit)
{
    for(new i = 0; i < 10000; i++) printf("%d", i);
}
print("end");

The threaded pseudo-statement is a convenient syntactic shortcut to enclosing a block in a pair of calls to thread_detach/thread_attach. The first function "detaches" the code from the main thread to a separate thread, and the second one does the opposite.

Synchronisation

Notice the argument in threaded(sync_explicit). In a threaded block, you may want to call native functions, but doing so could introduce race conditions (effects when the result of a process can be affected by the order of concurrent operations). For functions like GetPlayerPos, strcmp, or gettime, which have no side effects, it doesn't matter, but changing e.g. a server rule when somebody wants to read it may produce inconsistent behaviour.

To address this reason, using thread(sync_auto) will run the thread in the auto-sync mode, which will synchronize all native calls with the process tick of the server. The main code will run in parallel, but any native call will pause the execution and wait until the next process tick happens, then execute the call and resume the thread.

Use the auto-sync mode whenever you only have a small amount of non-CPU-intensive native calls, and the work is done only by your script. If the work is done by a native function instead, use threaded(sync_explicit), because otherwise the function would be run synchronously.

Callbacks

At the moment, due to the shared heap-stack memory block, at most one thread is allowed to execute code in a single AMX machine, including the main thread. This means that if a callback is to be executed, it cannot do so while there is a thread running the code in the AMX. The parameter of thread_detach specifies when the callback should happen.

The thread is paused whenever a native call in the auto-sync mode happens, when the threaded block ends, or when thread_sync is called. This native function pauses the thread until the next tick happens, doing effectively nothing in itself in the auto-sync mode (since the synchronisation happens there anyway).

If the thread is paused, the callback can proceed. In some cases, it is also safe for the thread to be paused without having to explicitly synchronise it, via sync_interrupt as the argument.

Consider the following code:

forward Work();
public Work()
{
    printf("A");
    threaded(sync_explicit)
    {
        printf("B");
        thread_sleep(500);
        printf("C");
        thread_sleep(500);
        printf("D");
        thread_sleep(500);
        printf("E");
    }
    printf("F");
}

forward Timer();
public Timer()
{
    printf("X");
}

public OnFilterScriptInit()
{
    CallLocalFunction(#Work, "");
    SetTimer(#Timer, 1000, false);
}

Work does no synchronisation, so Timer cannot be executed until the block ends. The output is ABCDEXF.

However, when thread_sync(); is put before printf("E");, it pauses the execution and executes any pending calls, resulting in the output ABCDXEF.

Tasks

The task API was not designed to be thread-safe, hence it is not advised to call task-related functions in a threaded block. It is possible to run threaded code in a task continuation, but not vice-versa. You can, however, call task_set_result after a threaded block, or inside if the thread is in the auto-sync mode.

Strings

Standard string modifying and comparison operations are thread-safe (when not done on the same string concurrently), but any function that accesses the string pool is not thread-safe.

Clone this wiki locally