C++ Actor Framework (CAF) — це сучасна та гнучка бібліотека для побудови одночасних і розподілених систем на C++. Спроектована для спрощення складнощів багатопотокового програмування, CAF надає можливість розробникам створювати високопродуктивні та ефективні додатки на основі акторів з мінімальними зусиллями.
Цей посібник вводить у основи акторних систем на C++, показуючи, як створювати акторів, керувати повідомленнями та ефективно розподіляти навантаження. Незалежно від того, чи ви новачок у моделі акторів, чи просто вивчаєте сучасні підходи до паралельного програмування, цей підручник надасть практичне введення у створення додатків на основі акторів за допомогою CAF.
Що таке актори
Модель акторів — це концептуальна модель, яка використовується для створення одночасних і розподілених систем. Вона спрощує управління комунікацією та обчисленнями в таких середовищах, вводячи концепцію акторів. Кожен актор працює незалежно і інкапсулює свій власний стан і поведінку.
Основні характеристики акторів:
- Інкапсуляція та ізоляція
- Кожен актор має свій власний стан і поштову скриньку (чергу повідомлень, які він отримує). Актори взаємодіють один з одним виключно через передачу повідомлень.
- Це усуває необхідність використання спільної пам'яті та зменшує ймовірність виникнення умов гонки.
2. Передача повідомлень:
- Актори надсилають і отримують повідомлення асинхронно.
- Кожне повідомлення обробляється послідовно, що забезпечує безпеку потоків без додаткових механізмів синхронізації, таких як замки.
3. Поведінка:
- Після отримання повідомлення актор може обробити його і оновити свій внутрішній стан, створити нових акторів для виконання завдань або надіслати повідомлення іншим акторам.
4. Поштові скриньки:
- Повідомлення чергуються в поштовій скриньці актора, чекаючи обробки. Актор бере по одному повідомленню для обробки.
- Якщо поштові скриньки порожні, актор залишається в режимі очікування.
5. Одночасність без спільного стану:
- На відміну від традиційної багатопотокової одночасності, модель акторів уникає спільного стану, що робить її придатною для масштабованих і розподілених систем.
C++ Actor Framework (CAF)
C++ Actor Framework (CAF) — це бібліотека з відкритим кодом, спроектована для спрощення створення одночасних і розподілених систем на C++. Вона реалізує модель акторів, надаючи розробникам інструменти для створення легких акторів, обробки передачі повідомлень та безперешкодного масштабування додатків. Щоб дізнатися більше про CAF, відвідайте його GitHub репозиторій.
Побудова бібліотеки CAF
Щоб почати працювати з C++ Actor Framework (CAF), ви можете клонувати його репозиторій з відкритим кодом на GitHub і побудувати його за допомогою CMake. Спочатку клонуйте репозиторій:
git clone https://github.com/actor-framework/actor-framework.git
cd actor-framework
Далі налаштуйте збірку для статичних бібліотек:
cmake -Bbuild -H. -DBUILD_SHARED_LIBS=OFF
Останній крок — скомпілювати і встановити CAF:
cmake --build build --target install
Це побудує та встановить CAF як статичну бібліотеку, готову для інтеграції у ваші C++ проекти. Також можна побудувати CAF як динамічну бібліотеку — просто переконайтеся, що збірка успішна і що ви зможете інтегрувати її в свою кодову базу. Для детальних інструкцій та варіантів, ознайомтесь з документацією CAF.
Примітка: обов'язково звертайтеся до документації для версії 1.0.2 або новіших, оскільки стара документація досі доступна, але її використання може призвести до багів.
Мінімальний приклад
У цьому розділі ми проведемо вас через створення базової акторної системи за допомогою C++ Actor Framework. Для простоти побудуємо мінімальну систему з трьох акторів: Main, Worker і Printer. Цей приклад демонструє основи створення акторів, передачі повідомлень та розподілу навантаження.
Main.cpp
Цей код в Main.cpp налаштовує базову акторну систему, використовуючи CAF для розподілу завдань з обробки даних.
Цей код створює три типи акторів: актор принтера, щоб виводити результати, кілька акторів-робітників, щоб обробляти дані, і головний актор, щоб розподіляти завдання серед робітників. Скоуп-актор (self
) керує системою, надсилаючи дані головному актору для розподілу та забезпечуючи завершення всіх акторів перед завершенням системи.
#include "caf/all.hpp"
#include "MainActor.hpp"
#include "WorkerActor.hpp"
#include "PrinterActor.hpp"
using namespace caf;
using namespace std::literals;
void caf_main(actor_system& sys, const config& cfg)
{
// Створення актора принтера
auto printer = sys.spawn(printer_actor);
// Створення пулу акторів-робітників
std::vector<actor> workers;
int num_workers = 3; // Кількість робітників
for (int i = 0; i < num_workers; ++i) {
workers.push_back(sys.spawn(worker_actor, printer));
}
// Створення головного актора
auto main = sys.spawn(main_actor, workers);
// Дані для обробки
std::vector<int> data_array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// Створення скоуп-актора для надсилання повідомлень і управління життєвим циклом акторів
scoped_actor self{sys};
// Надсилання масиву даних головному актору для розподілу
self->mail(data_array).send(main_actor);
// Чекати, поки всі інші актори завершать свої завдання
self->await_all_other_actors_done();
}
CAF_MAIN(id_block::system)
MainActor.cpp
Цей розділ реалізує MainActor у CAF, відповідальний за розподіл вхідного масиву чисел серед пулу акторів-робітників. Він розраховує навантаження для кожного робітника, ділить дані на партії і надсилає їх відповідним робітникам. Коли всі дані будуть розподілені, головний актор записує результати та коректно завершує свою роботу.
#include "MainActor.hpp"
// Конструктор: ініціалізація стану головного актора з акторів-робітників
main_actor_state::main_actor_state(main_actor::pointer selfptr, std::vector<actor> w)
: self(selfptr), workers(std::move(w)) {}
// Описуємо поведінку головного актора
main_actor::behavior_type main_actor_state::make_behavior() {
return {
// Обробка отримання масиву чисел для обробки
[this](const std::vector<int>& data_array) {
if (!data_array.empty()) {
int num_workers = workers.size();
int total_items = data_array.size();
// Розподіл даних рівномірно серед робітників
int base_size = total_items / num_workers;
int extra_items = total_items % num_workers;
auto current = data_array.begin();
for (int i = 0; i < num_workers; ++i) {
// Розрахунок кількості елементів для цього робітника
int worker_batch_size = base_size + (i < extra_items ? 1 : 0);
// Створення партії чисел для робітника
std::vector<int> batch(current, current + worker_batch_size);
if (!batch.empty())
{
self->mail(batch).send(workers[i]);
}
// Переміщення ітератора вперед на розмір партії
current += worker_batch_size;
}
self->println("Data distribution completed. {} items processed by {} workers.", total_items, num_workers);
} else {
self->println("No data to distribute.");
}
// Завершення роботи головного актора після обробки
self->quit();
}
};
}
WorkerActor.cpp
Цей WorkerActor обробляє партію чисел, підносячи кожне число до квадрату і надсилаючи результати в PrinterActor через mail()
для передачі повідомлень.
Після того як всі числа будуть оброблені, робітник коректно завершить свою роботу, викликавши self->quit()
.
#include "WorkerActor.hpp"
// Конструктор: ініціалізація стану робітника з актором принтера
worker_actor_state::worker_actor_state(worker_actor::pointer selfptr, actor printer)
: self(selfptr), printer_actor(std::move(printer)) {}
// Функція для обробки партії чисел
void worker_actor_state::process_numbers(const std::vector<int>& numbers) {
for (const auto& num : numbers) {
int squared = num * num; // Підносимо число до квадрату
self->mail(squared).send(printer_actor); // Надсилаємо результат в принтер
}
self->quit(); // Завершуємо роботу робітника після обробки
}
// Описуємо поведінку актора-робітника
worker_actor::behavior_type worker_actor_state::make_behavior() {
return {
[this](const std::vector<int>& numbers) {
process_numbers(numbers); // Обробляємо отриману партію чисел
}
};
}
PrinterActor.cpp
Цей PrinterActor просто отримує числа як повідомлення та виводить їх на консоль за допомогою std::cout
.
#include "PrinterActor.hpp"
#include <iostream>
// Конструктор: ініціалізація стану принтера
printer_actor_state::printer_actor_state(printer_actor::pointer selfptr)
: self(selfptr) {}
// Описуємо поведінку актора-принтера
printer_actor::behavior_type printer_actor_state::make_behavior() {
return {
[this](int number) {
// Виводимо отримане число на консоль за допомогою std::cout
std::cout << "Received number: " << number << std::endl;
}
};
}
Додаткові ресурси
Якщо ви хочете глибше зануритись у програмування на основі акторів або розширити свої знання, рекомендую ознайомитись з документацією CAF та іншими туторіалами, доступними онлайн. Для більш складного прикладу перегляньте цей репозиторій на GitHub, який поєднує CAF з мультипроцесорністю Python і комунікацією на основі сокетів, щоб продемонструвати взаємодію між C++ і Python. Ці ресурси допоможуть вам розвивати масштабовані та високопродуктивні системи, використовуючи модель акторів.
Підсумки
Цей посібник ознайомив вас з основами програмування на основі акторів за допомогою C++ Actor Framework. Ви дізналися, як створювати акторів, керувати завданнями та розподіляти навантаження між кількома компонентами.
Приклади, представлені в цьому матеріалі, забезпечують міцну основу для вивчення більш складних функцій CAF. З практикою та експериментами ви будете готові до проектування надійних і масштабованих одночасних систем для різноманітних застосувань.📈🌟
Перекладено з: Getting Started with Actor-Based Programming in C++ Using the C++ Actor Framework