asynchrony 0.0.0
Add asynchrony to your C++ applications using standard C++20
Loading...
Searching...
No Matches
Usage Guide

Overview

This guide covers the main components of the asynchrony library and how to use them effectively.

Simple Worker

The simple_worker is the simplest component - it provides a single background thread that processes items from a queue sequentially.

Basic Usage

#include "siddiqsoft/simple_worker.hpp"
struct WorkItem {
int id;
std::string data;
};
int main() {
// Create a worker with a callback
[](auto&& item) {
std::cout << "Processing item " << item.id << ": " << item.data << std::endl;
}
};
// Queue items
worker.queue(WorkItem{1, "first"});
worker.queue(WorkItem{2, "second"});
worker.queue(WorkItem{3, "third"});
// Wait for completion
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
Implements a simple queue + semaphore driven asynchronous processor.
void queue(T &&item)
Queue item into this worker thread's deque.

Thread Priority

You can set thread priority using the template parameter:

// Normal priority (0)
// High priority (5)
// Low priority (-5)
siddiqsoft::simple_worker<Task, -5> worker3{callback};

Priority range: -10 to +10

Exception Handling

Exceptions in the callback are caught and logged to stderr:

[](auto&& item) {
try {
// Your processing logic
if (item.invalid) throw std::runtime_error("Invalid item");
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
};

Lifetime Management

The worker will wait for the queue to empty before shutting down:

{
worker.queue(task1);
worker.queue(task2);
} // Destructor waits for queue to empty before returning

Simple Pool

The simple_pool distributes work across multiple threads using a shared queue. All threads wait on the same queue, so there's minimal contention.

Basic Usage

#include "siddiqsoft/simple_pool.hpp"
int main() {
// Create a pool with default thread count (hardware_concurrency)
[](auto&& item) {
std::cout << "Thread " << std::this_thread::get_id()
<< " processing item " << item.id << std::endl;
}
};
// Queue items
for (int i = 0; i < 100; ++i) {
pool.queue(WorkItem{i, "data-" + std::to_string(i)});
}
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
Implements a single deque based vector of jthreads. All threads wait on the next available item via s...
void queue(T &&item)
Queue item into the deque (takes "ownership" of the item).

Custom Thread Count

// Create a pool with exactly 4 threads
// Create a pool with default thread count
siddiqsoft::simple_pool<WorkItem, 0> pool{callback}; // 0 = use hardware_concurrency()

Performance Considerations

  • Use simple_pool when tasks are CPU-bound or have similar execution times
  • Use roundrobin_pool when tasks have variable execution times
  • Increase thread count for I/O-bound tasks
  • Decrease thread count for CPU-bound tasks (typically = hardware_concurrency)

Round-Robin Pool

The roundrobin_pool uses per-thread queues and distributes work in a round-robin fashion. This reduces contention compared to simple_pool.

Basic Usage

#include "siddiqsoft/roundrobin_pool.hpp"
int main() {
[](auto&& item) {
std::cout << "Processing item " << item.id << std::endl;
}
};
for (int i = 0; i < 1000; ++i) {
pool.queue(WorkItem{i, "data"});
}
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
Implements a lock-free round robin work allocation into vector of simple_worker<T>.
void queue(T &&item)
Queue item into one of the thread's queue.

Custom Thread Count

// Create a round-robin pool with exactly 8 threads

Round-Robin vs Simple Pool

Aspect Simple Pool Round-Robin Pool
Queue Type Single shared queue Per-thread queues
Contention Higher Lower
Load Balancing Automatic Round-robin
Best For Uniform task times Variable task times
Memory Lower Higher

Periodic Worker

Execute a function at regular intervals.

Basic Usage

#include "siddiqsoft/periodic_worker.hpp"
int main() {
[]() {
std::cout << "Tick! " << std::chrono::system_clock::now() << std::endl;
},
std::chrono::milliseconds(500) // Execute every 500ms
};
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
Implements a simple queue + semaphore driven asynchronous processor.

Priority

// High priority periodic worker
siddiqsoft::periodic_worker<5> timer{callback, interval};

Named Periodic Worker

// Named periodic worker (useful for debugging)
callback,
std::chrono::milliseconds(1000),
"my-timer"
};

Resource Pool

Manage a pool of reusable resources (e.g., database connections).

Basic Usage

#include "siddiqsoft/resource_pool.hpp"
class DatabaseConnection {
public:
void execute(const std::string& query) { /* ... */ }
};
int main() {
// Checkout a resource
auto conn = pool.checkout();
conn.execute("SELECT * FROM users");
// Checkin returns the resource to the pool
pool.checkin(std::move(conn));
return 0;
}
Implements a resource pool that stores objects of type T. Said objects can be shared_ptr or unique_pt...
void checkin(T &&rsrc)
Insert a new element or return a borrowed element.

Resource Pool Methods

// Get current pool size
auto size = pool.size();
// Checkout a resource (throws if empty)
auto resource = pool.checkout();
// Return a resource to the pool
pool.checkin(std::move(resource));
// Clear all resources
pool.clear();
auto size()
Get the current size of the pool FIX: Removed unnecessary empty check - return size unconditionally T...
void clear()
Clear all items from the pool FIX: Removed unnecessary empty check - clear() is safe on empty deque.

Best Practices

Lifetime Management

  • Keep the worker/pool alive as long as you're queuing work
  • Destruction waits for the queue to empty before shutting down threads
  • Use RAII to ensure proper cleanup

Callback Design

  • Keep callbacks lightweight and fast
  • Avoid blocking operations in callbacks
  • Handle exceptions within the callback
  • Use move semantics for efficiency

Thread Safety

  • The queue() method is thread-safe
  • Callbacks are executed in worker threads
  • Protect shared state accessed from callbacks with mutexes

Performance Tips

  • Use roundrobin_pool for variable-duration tasks
  • Use simple_pool for uniform-duration tasks
  • Batch small tasks to reduce overhead
  • Monitor queue depth for bottlenecks

Advanced Topics

Custom Data Types

Your data type must be move-constructible:

struct MyTask {
std::string data;
std::vector<int> values;
// Must be move-constructible
MyTask(MyTask&&) = default;
MyTask& operator=(MyTask&&) = default;
// Copy is optional
MyTask(const MyTask&) = delete;
MyTask& operator=(const MyTask&) = delete;
};

JSON Serialization

If nlohmann/json is available, you can serialize worker state:

#include <nlohmann/json.hpp>
#include "siddiqsoft/simple_worker.hpp"
auto json = worker.toJson();
std::cout << json.dump(2) << std::endl;

The JSON output includes:

  • _typver: Library version identifier
  • itemsSize: Current queue size
  • queueCounter: Total items queued
  • itemsQueued: Total items added
  • itemsPopped: Total items processed
  • itemsOutstanding: Items still in queue
  • threadPriority: Thread priority level
  • outstandingCallback: Callbacks currently executing
  • waitInterval: Wait timeout in milliseconds