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;
}
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:
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;
}
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
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
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;
}
Implements a simple queue + semaphore driven asynchronous processor.
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() {
auto conn = pool.checkout();
conn.execute("SELECT * FROM users");
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
auto resource = pool.checkout();
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;
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