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() {
[](auto&& item) {
std::cout << "Processing item " << item.id << ": " << item.data << std::endl;
}
};
worker.
queue(WorkItem{1,
"first"});
worker.
queue(WorkItem{2,
"second"});
worker.
queue(WorkItem{3,
"third"});
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
void queue(T &&item) noexcept(false)
Queue a work item for processing.
Thread Priority
You can set thread priority using the template parameter:
Priority range: -10 to +10
Exception Handling
Exceptions in the callback are caught and logged to stderr:
[](auto&& item) {
try {
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:
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() {
[](auto&& item) {
std::cout << "Thread " << std::this_thread::get_id()
<< " processing item " << item.id << std::endl;
}
};
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;
}
void queue(T &&item)
Queue a work item for processing.
Custom Thread Count
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;
}
void queue(T &&item)
Queue a work item for processing.
Custom Thread Count
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)
};
std::this_thread::sleep_for(std::chrono::seconds(5));
return 0;
}
Priority
Named Periodic Worker
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() {
conn.execute("SELECT * FROM users");
pool.checkin(std::move(conn));
return 0;
}
auto checkout() -> resource_wrap< T >
Resource Pool Methods
The objects you store in the resource_pool<T> are returned via a resource_wrap<T> object which is designed to offer you easy conversion to the underlying stored type T. The resource_wrap<T> when it goes out of scope will return the object to the pool.
You must use move semantics to allow the resource_pool to hold the resource. If you're using a shared_ptr then make sure you understand that you're going to std::move into the pool.
pool.checkin(std::move(resource));
auto size()
Get the current size of the pool.
void clear()
Clear all items from the pool.
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;
MyTask(MyTask&&) = default;
MyTask& operator=(MyTask&&) = default;
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