Creating and Using a Queue in Dragon Native
The Dragon Native Queue implementation is Dragon’s specialized implementation of a Queue, working in both single and multi node settings. It is interoperable in all supported languages. The API is similar to Python’s Multiprocessing.Queue in many ways, but has a number of extensions and simplifications. In particular, the queue can be initialized as joinable, which allows to join on the completion of an item.
Using a Queue with Python
The Dragon Native Queue in Python mirrors the multiprocessing.Queue interface
but works across multiple nodes and supports joinable queues. Import it from dragon.native.queue:
1import dragon
2from multiprocessing import set_start_method
3from dragon.native.process import Process
4from dragon.native.queue import Queue
5
6def producer(q):
7 for i in range(5):
8 q.put(f"item-{i}")
9 q.put(None) # sentinel to signal completion
10
11def consumer(q):
12 while True:
13 item = q.get()
14 if item is None:
15 break
16 print(f"Received: {item}", flush=True)
17
18if __name__ == "__main__":
19 set_start_method("dragon")
20
21 q = Queue()
22 p_prod = Process(target=producer, args=(q,))
23 p_cons = Process(target=consumer, args=(q,))
24
25 p_cons.start()
26 p_prod.start()
27 p_prod.join()
28 p_cons.join()
The queue is serializable: it can be passed as an argument to any managed process, on any node in the
Dragon runtime, just like a multiprocessing.Queue . For joinable queues,
initialize with joinable=True and call task_done() after processing
each item, then use join() to wait for all items to be processed:
1import dragon
2from multiprocessing import set_start_method
3from dragon.native.process import Process
4from dragon.native.queue import Queue
5
6def worker(q):
7 while True:
8 item = q.get()
9 if item is None:
10 q.task_done() # balance the sentinel's task count
11 break
12 print(f"Processing: {item}", flush=True)
13 q.task_done()
14
15if __name__ == "__main__":
16 set_start_method("dragon")
17
18 q = Queue(joinable=True)
19 p = Process(target=worker, args=(q,))
20 p.start()
21
22 for i in range(10):
23 q.put(f"work-{i}")
24
25 q.join() # block until all items have been processed
26 q.put(None) # stop the worker
27 p.join()
See dragon.native.queue.Queue for the full API reference.
Using a Queue with C++
Dragon’s native Queue has a C++ interface provided by the dragon::Queue<T>
class template declared in <dragon/queue.hpp>. There is no separate C API;
C and C++ code both use this C++ template. A Queue is created in Python and then
attached from C++ using its serialized descriptor, so the Queue’s lifetime is
managed by the Python code that created it.
The T type parameter is a serializable value type. Dragon ships several
ready-made serializable wrappers in <dragon/serializable.hpp> (for example
dragon::SerializableString, dragon::SerializableByteBuffer,
dragon::SerializableScalar, and dragon::SerializableVector). You can also
implement your own by subclassing dragon::SerializableBase.
First, create the Queue in Python and pass its serialized descriptor to the C++
program. Use the Queue’s serialize() method,
which returns a base64-encoded descriptor intended for the C++ Queue to attach to:
1import dragon
2from multiprocessing import set_start_method
3from dragon.native.queue import Queue
4import subprocess
5
6if __name__ == "__main__":
7 set_start_method("dragon")
8
9 q = Queue()
10 serialized = q.serialize() # base64 FLI descriptor for the C++ side
11
12 result = subprocess.run(
13 ["./cpp_queue_demo", serialized],
14 capture_output=True, text=True,
15 )
16 print("C++ stdout:", result.stdout.strip())
The C++ program attaches to that Queue, then puts and gets a value:
1#include <dragon/queue.hpp>
2#include <dragon/serializable.hpp>
3#include <iostream>
4
5int main(int argc, char* argv[]) {
6 if (argc < 2) {
7 std::cerr << "usage: " << argv[0] << " <serialized_queue_descr>\n";
8 return 1;
9 }
10
11 try {
12 // Attach to the Python-created queue (NULL => use the default pool).
13 dragon::Queue<dragon::SerializableString> q(argv[1], nullptr);
14
15 // Put a value, then get it back.
16 dragon::SerializableString msg(std::string("Hello from C++"));
17 q.put(msg);
18
19 dragon::SerializableString got = q.get();
20 std::cout << "C++ received: " << got.getVal() << std::endl;
21 } catch (const dragon::DragonError& e) {
22 std::cerr << "DragonError: " << e.err_str() << std::endl;
23 return 1;
24 }
25 return 0;
26}
Compile against the Dragon headers and link against libdragon. The
dragon-config command emits the correct include and link flags for your
installation:
g++ -std=c++17 cpp_queue_demo.cpp \
$(dragon-config -o) $(dragon-config -l) -o cpp_queue_demo
Then run the Python driver under the Dragon runtime. The C++ subprocess inherits the runtime environment, so it can attach to the default memory pool:
dragon run_cpp_queue.py
The program prints:
C++ stdout: C++ received: Hello from C++
See <dragon/queue.hpp> for the full C++ Queue API, including put, get,
get_nowait, poll, and their timeout-aware overloads.