Events and Synchronization

As soon as you need to know if a tasks in a queue is already executed or you think about task parallelism you need events. This allows to describe dependencies between tasks in different queues without blocking the host thread. In alpaka, queues describe execution order, and events describe dependencies between queues.

Queue Rules

  • Operations inside one queue execute in FIFO order.

  • Different queues may run independently.

  • onHost::wait(queue) waits until all work in that queue is complete.

  • onHost::wait(event) waits until the event has been processed which means that all previous enqueues tasks are completed.

  • queue1.waitFor(event) inserts a dependency so work in queue1 enqueued after starts only after the event is reached.

Use Cases

Multiple queues are primarily used in the following scenarios:

  • Host and device queues: Run independent tasks on the host CPU and the device (e.g., a GPU) and synchronize both devices.

  • Many queues for many devices: For example, in multi-GPU systems, each GPU requires its own queue.

  • Many queues for a single device: Enables better utilization of a single device. The performance benefits depend on the API.

Creating and Using an Event

In the following example, we create two queues (queue0 and queue1). Both execute functions on the host via enqueueHostFn(). Without synchronization between the queues using events, a race condition is possible. It is possible that queue1 increments the value valueA before valueA is set to 41 in queue0.

// Create two queues on the same device.
onHost::Queue queue0 = device.makeQueue();
onHost::Queue queue1 = device.makeQueue();
int valueA = 0;
int valueB = 0;

// Create an event.
auto event = device.makeEvent();

queue0.enqueueHostFn([&valueA]() { valueA = 41; });
/* Enqueue an event to connect two queues without forcing the host to block between them.
Another queue can wait until this point is reached. */
queue0.enqueue(event);
queue0.enqueueHostFn([&valueB]() { valueB = 23; });

/* A task enqueued after the event will wait until the event is complete before it starts executing.
 * That does not mean that the followed calls are blocking, if a call is blocking the executor depends on the queue
 * kind.
 */
queue1.waitFor(event);
/* This operation is guaranteed to be called after valueA in queue0 is updated.
 * The update of valueB in queue0 can run in parallel to the additional increment of valueA in queue1.
 */
queue1.enqueueHostFn([&valueA]() { valueA += 1; });

onHost::wait(queue0);
onHost::wait(queue1);

// An enqueued event can be checked for completion. This is useful for example to check if a long-running task is
// finished.
CHECK(event.isComplete());

Complete Source File

160_events.cpp
 1/* Copyright 2026 René Widera
 2 * SPDX-License-Identifier: ISC
 3 */
 4
 5#include "docsTest.hpp"
 6
 7#include <alpaka/alpaka.hpp>
 8
 9#include <catch2/catch_template_test_macros.hpp>
10#include <catch2/catch_test_macros.hpp>
11
12using namespace alpaka;
13
14TEMPLATE_LIST_TEST_CASE("tutorial events and synchronization", "[docs]", docs::test::TestBackends)
15{
16    auto selector = onHost::makeDeviceSelector(TestType::makeDict()[object::deviceSpec]);
17    if(!selector.isAvailable())
18        return;
19    onHost::concepts::Device auto device = selector.makeDevice(0);
20    // Create two queues on the same device.
21    onHost::Queue queue0 = device.makeQueue();
22    onHost::Queue queue1 = device.makeQueue();
23    int valueA = 0;
24    int valueB = 0;
25
26    // Create an event.
27    auto event = device.makeEvent();
28
29    queue0.enqueueHostFn([&valueA]() { valueA = 41; });
30    /* Enqueue an event to connect two queues without forcing the host to block between them.
31    Another queue can wait until this point is reached. */
32    queue0.enqueue(event);
33    queue0.enqueueHostFn([&valueB]() { valueB = 23; });
34
35    /* A task enqueued after the event will wait until the event is complete before it starts executing.
36     * That does not mean that the followed calls are blocking, if a call is blocking the executor depends on the queue
37     * kind.
38     */
39    queue1.waitFor(event);
40    /* This operation is guaranteed to be called after valueA in queue0 is updated.
41     * The update of valueB in queue0 can run in parallel to the additional increment of valueA in queue1.
42     */
43    queue1.enqueueHostFn([&valueA]() { valueA += 1; });
44
45    onHost::wait(queue0);
46    onHost::wait(queue1);
47
48    // An enqueued event can be checked for completion. This is useful for example to check if a long-running task is
49    // finished.
50    CHECK(event.isComplete());
51    CHECK(valueA == 42);
52    CHECK(valueB == 23);
53}