Lihatlah SObjectizer jika Anda ingin menggunakan Aktor atau CSP dalam proyek C ++ Anda


Beberapa kata tentang SObjectizer dan sejarahnya


SObjectizer adalah kerangka C ++ agak kecil yang menyederhanakan pengembangan aplikasi multithreaded. SObjectizer memungkinkan pengembang untuk menggunakan pendekatan dari model Aktor, Terbitkan-Berlangganan, dan Berkomunikasi Proses Berurutan (CSP). Ini adalah proyek OpenSource yang didistribusikan di bawah lisensi BSD-3-CLAUSE.


SObjectizer memiliki sejarah panjang. SObjectizer sendiri lahir pada tahun 2002 sebagai proyek SObjectizer-4. Tetapi didasarkan pada ide-ide dari ObjectADA SCADA sebelumnya yang dikembangkan antara 1995 dan 2000. SObjectizer-4 bersumber terbuka pada tahun 2006, tetapi evolusinya dihentikan segera setelah itu. Versi baru SObjectizer dengan nama SObjectizer-5 dimulai pada 2010 dan bersumber terbuka pada 2013. Evolusi SObjectizer-5 masih dalam proses dan SObjectizer-5 telah memasukkan banyak fitur baru sejak 2013.


SObjectizer kurang lebih dikenal di segmen Internet Rusia, tetapi hampir tidak dikenal di luar exUSSR. Itu karena SObjectizer terutama digunakan untuk proyek-proyek lokal di negara-negara exUSSR dan banyak artikel, presentasi, dan pembicaraan tentang SObjectizer dalam bahasa Rusia.


Ceruk untuk SObjectizer dan alat serupa


Multithreading digunakan dalam komputasi Paralel maupun dalam komputasi Bersamaan . Tetapi ada perbedaan besar antara komputasi Paralel dan Bersamaan. Dan, sebagai konsekuensinya, ada alat yang ditargetkan komputasi Paralel, dan ada alat untuk komputasi Bersamaan, dan mereka berbeda.


Secara kasar, komputasi paralel adalah tentang menggunakan beberapa inti untuk mengurangi waktu perhitungan. Misalnya, transcoding file video dari satu format ke format lain dapat memakan waktu satu jam pada satu inti CPU, tetapi hanya 15 menit pada empat inti CPU. Alat-alat seperti OpenMP, Intel TBB, HPX atau cpp-taskflow dirancang untuk digunakan dalam komputasi Paralel. Dan alat-alat itu mendukung pendekatan area yang sesuai, seperti pemrograman berbasis tugas atau aliran data.


Komputasi bersamaan adalah tentang menangani banyak (mungkin berbeda) tugas pada saat yang bersamaan. Server database atau MQ-broker dapat menjadi contoh yang baik: server harus menerima koneksi, membaca dan mem-parsing data dari koneksi yang diterima, menangani permintaan yang diterima (melakukan beberapa tindakan untuk setiap permintaan), mengirim respons, dan sebagainya. Sebenarnya, tidak perlu menggunakan multithreading dalam komputasi bersamaan: semua tugas itu dapat dilakukan hanya pada satu utas pekerja. Tetapi penggunaan multithreading dan beberapa core CPU dapat membuat aplikasi Anda lebih berkinerja, terukur dan responsif.


Pendekatan seperti Actor Model atau CSP dimaksudkan untuk menangani komputasi secara bersamaan. Contoh-contoh baik dari Aktor penggunaan dalam area komputasi secara bersamaan adalah proyek InfineSQL dan Yandex Message-Queue . Kedua proyek tersebut menggunakan aktor di dalam.


Jadi alat seperti SObjectizer, QP / C ++ atau CAF, yang mendukung Actor Model, berguna dalam menyelesaikan tugas-tugas dari area komputasi Concurrent. Ini berarti bahwa penggunaan SObjectizer mungkin tidak akan memberi Anda apa pun dalam tugas-tugas seperti konversi aliran video. Tetapi Anda bisa mendapatkan hasil yang sangat berbeda dengan menerapkan broker pesan di atas SObjectizer.


Penafian


Penggunaan model Aktor- atau CSP dapat memberi Anda manfaat besar dalam beberapa tugas, tetapi tidak ada jaminan bahwa model tersebut sesuai untuk masalah khusus Anda. Pembicaraan tentang penerapan model Aktor- atau CSP berada di luar cakupan artikel itu. Mari kita asumsikan bahwa model Aktor atau / dan CSP berlaku untuk tugas Anda dan Anda tahu cara menggunakannya secara efisien.


SObjectizer apa yang dapat diberikan kepada pengguna?


Prinsip shared-nothing dan fire-and-forget baru saja keluar dari kotak


Penggunaan Aktor mengasumsikan tidak adanya data bersama. Setiap aktor memiliki datanya dan data ini tidak dapat dilihat oleh orang lain. Ini adalah prinsip shared-nothing yang terkenal dalam pengembangan aplikasi terdistribusi, misalnya. Dalam aplikasi multithread, prinsip shared-nothing memiliki manfaat penting: memungkinkan untuk menghindari masalah berbahaya seperti bekerja dengan data bersama seperti deadlock dan data-race.


Interaksi antara aktor (agen) di SObjectizer hanya dilakukan melalui pesan asinkron. Satu agen mengirim pesan ke agen lain dan operasi ini tidak memblokir pengirim (dalam kasus umum).


Interaksi asinkron memungkinkan penggunaan prinsip lain yang bermanfaat: api-dan-lupakan . Ketika beberapa agen perlu beberapa operasi untuk dilakukan, ia mengirim (menembak) pesan dan melanjutkan pekerjaannya. Dalam sebagian besar kasus, pesan akan diterima dan diproses.


Misalnya, mungkin ada agen yang membaca koneksi yang diterima dan mem-parsing data yang masuk. Jika seluruh PDU dibaca dan diuraikan agen hanya mengirimkan PDU itu ke agen-prosesor lain dan kembali membaca / mem-parsing data baru yang masuk.


Dispatcher


Dispatcher adalah salah satu pilar SObjectizer. Dispatcher memberikan konteks kerja (alias utas pekerja) bahwa agen akan menangani pesan yang masuk. Alih-alih membuat utas pekerja (atau kumpulan utas) secara manual, pengguna membuat dispatcher dan mengikat agen kepada mereka. Seorang pengguna dapat membuat sebanyak mungkin dispatcher dalam suatu aplikasi sesuai keinginannya.


Hal terbaik dengan dispatcher dan agen di SObjectizer adalah pemisahan konsep: dispatcher bertanggung jawab untuk mengelola konteks kerja dan memiliki antrian pesan, agen melakukan logika aplikasi dan tidak peduli dengan konteks pekerja. Ini memungkinkan pemindahan agen dari satu operator ke operator lain secara harfiah dengan satu klik. Kemarin agen bekerja pada dispatcher one_thread, hari ini kita bisa mengubahnya menjadi dispatcher active_obj, dan besok kita bisa rebind ke dispatcher thread_pool. Tanpa mengubah garis dalam implementasi agen.


Ada delapan jenis dispatcher di SObjectizer-5.6.0 (dan satu lagi dapat ditemukan di proyek pendamping so5extra): mulai dari yang sangat sederhana (one_thread atau thread_pool) hingga yang canggih (seperti adv_thread_pool atau prio_dedicated_threads :: one_per_prio). Dan pengguna dapat menulis operator sendiri untuk kondisi tertentu.


Mesin status hierarkis adalah fungsi bawaan


Agen (aktor) dalam SObjectizer adalah mesin negara: reaksi pada pesan masuk tergantung pada keadaan agen saat ini. SObjectizer mendukung sebagian besar fitur mesin negara hierarkis (HSM): status bersarang, riwayat dalam dan dangkal untuk keadaan, penangan on_enter / on_exit, batas waktu untuk tetap berada dalam keadaan. Hanya status orthogonal yang tidak didukung di SObjectizer sekarang (kami tidak melihat perlunya fitur itu di proyek kami, dan tidak ada yang meminta kami untuk menambahkan dukungan untuk fitur itu).


Saluran seperti CSP baru saja keluar dari kotak


Tidak perlu menggunakan agen SObjectizer (alias aktor). Seluruh aplikasi dapat dikembangkan hanya menggunakan objek std::thread dan mchains SObjectizer (alias saluran CSP) . Dalam hal itu pengembangan aplikasi dengan SObjectizer akan sedikit mirip dengan pengembangan dalam bahasa Go (termasuk analog dari konstruk select Go yang memungkinkan untuk menunggu pesan dari beberapa saluran).


Kereta SObjectizer dapat memiliki fitur yang sangat penting: mekanisme tekanan balik terpadu. Jika pengguna membuat mchain terbatas ukuran dan kemudian mencoba mendorong pesan ke mchain penuh operasi pengiriman dapat memblokir pengirim untuk beberapa waktu. Hal ini memungkinkan untuk menyelesaikan masalah yang terkenal dengan produsen cepat dan konsumen lambat.


Kereta SObjectizer memiliki fitur menarik lainnya: mchain dapat digunakan sebagai alat distribusi beban yang sangat sederhana. Beberapa utas dapat menunggu diterima dari mchain yang sama pada saat yang sama. Jika pesan baru dikirim ke mchain itu, hanya satu utas yang akan membaca dan menangani pesan itu.


Hanya sebagian aplikasi yang dapat menggunakan SObjectizer


Tidak perlu menggunakan SObjectizer di setiap bagian aplikasi. Hanya sebagian dari aplikasi yang dapat dikembangkan dengan menggunakan SObjectizer. Jadi, jika Anda sudah menggunakan Qt atau wxWidgets, atau Boost.Asio sebagai kerangka kerja utama untuk aplikasi Anda, dimungkinkan untuk menggunakan SObjectize hanya dalam satu submodule dari aplikasi Anda.


Kami memiliki pengalaman tentang penggunaan SObjectizer untuk pengembangan perpustakaan yang menyembunyikan penggunaan SObjectizer sebagai detail implementasi. API publik dari perpustakaan-perpustakaan itu sama sekali tidak memaparkan keberadaan SObjectizer. SObjectizer sepenuhnya di bawah kendali perpustakaan: perpustakaan mulai dan menghentikan SObjectizer sesuai kebutuhan. Pustaka tersebut digunakan dalam aplikasi yang sama sekali tidak mengetahui keberadaan SObjectizer.


Jika SObjectizer hanya digunakan di bagian aplikasi, ada tugas komunikasi antara SObjectizer dan non-SObjectizer-bagian aplikasi. Tugas ini mudah diselesaikan: pesan dari komponen non-SObjectizer ke SObjectizer dapat dikirim melalui mekanisme SObjectizer pengiriman pesan biasa. Pesan di arah yang berlawanan dapat dikirim melalui kereta.


Anda dapat menjalankan beberapa instance SObjectizer secara bersamaan


SObjectizer memungkinkan dijalankannya beberapa instance SObjectizer (disebut SObjectizer Environment) dalam satu aplikasi secara bersamaan. Setiap Lingkungan SObjectizer tidak tergantung pada lingkungan lain seperti itu.


Fitur ini sangat berharga dalam situasi di mana Anda harus membangun aplikasi dari beberapa modul independen. Beberapa modul dapat menggunakan SObjectizer, beberapa tidak. Modul-modul yang memerlukan SObjectizer dapat menjalankan salinan Lingkungan SObjectizer dan yang tidak akan mempengaruhi modul-modul lain dalam aplikasi.


Pengatur waktu adalah bagian dari SObjectizer


Dukungan timer dalam bentuk pesan yang tertunda dan berkala adalah salah satu pilar SObjectizer. SObjectizer memiliki beberapa implementasi mekanisme pengatur waktu (timer_wheel, timer_heap, dan timer_list) dan dapat menangani puluhan, ratusan dan ribuan juta pengatur waktu dalam suatu aplikasi. Seorang pengguna dapat memilih mekanisme penghitung waktu yang paling tepat untuk suatu aplikasi. Selain itu, pengguna dapat memberikan implementasi timer_thread / timer_manager sendiri jika tidak ada yang standar yang sesuai dengan kondisi pengguna.


SObjectizer memiliki berbagai titik penyesuaian dan opsi penyetelan


SObjectizer memungkinkan penyesuaian beberapa mekanisme penting. Misalnya, pengguna dapat memilih salah satu implementasi standar timer_thread (atau timer_manager). Atau bisa memberikan implementasinya sendiri. Seorang pengguna dapat memilih implementasi dari objek kunci yang digunakan oleh antrian pesan dalam dispatcher SObjectizer. Atau bisa memberikan implementasinya sendiri.


Seorang pengguna dapat mengimplementasikan operator sendiri. Seorang pengguna dapat mengimplementasikan kotak pesannya sendiri. Seorang pengguna dapat mengimplementasikan amplop pesannya sendiri. Seorang pengguna dapat mengimplementasikan event_queue_hook sendiri. Dan sebagainya.


Di mana SObjectizer dapat atau tidak dapat digunakan?


Jauh lebih mudah untuk mengatakan di mana SObjectizer tidak dapat digunakan dengan alasan objektif. Jadi kami memulai diskusi dengan menghitung area-area tersebut dan kemudian kami akan memberikan beberapa contoh penggunaan SObjectizer di masa lalu (dan tidak hanya di masa lalu).


Di mana SObjectizer tidak dapat digunakan?



Seperti yang telah dikatakan di atas, model Actor dan CSP bukanlah pilihan yang baik untuk komputasi berperforma tinggi dan area lain dari komputasi Paralel. Jadi jika Anda harus melakukan beberapa matriks atau stream video transcode maka alat seperti OpenMP, Intel TBB, cpp-taskflow, HPX atau MPI akan lebih cocok.


Sistem real-time yang sulit


Terlepas dari kenyataan bahwa SObjectizer berakar pada sistem SCADA, implementasi SObjectizer (alias SObjectizer-5) saat ini tidak dapat digunakan dalam sistem waktu nyata yang sulit. Ini terutama karena penggunaan memori dinamis dalam implementasi SObjectizer: pesan adalah objek yang dialokasikan secara dinamis (namun, SObjectizer dapat menggunakan objek yang telah dialokasikan sebelumnya sebagai pesan), operator menggunakan memori dinamis untuk antrian pesan, bahkan batas waktu untuk status agen menggunakan objek yang dialokasikan secara dinamis untuk melakukan pengecekan waktu.


Sayangnya, istilah "waktu nyata" sangat sering digunakan di dunia modern. Sering dikatakan tentang layanan web waktu nyata, seperti "aplikasi web waktu nyata" atau "analisis web waktu nyata" dan seterusnya. Istilah "on-line" atau "live" lebih tepat untuk aplikasi seperti itu daripada istilah "real-time", bahkan dalam bentuk "real-time lunak". Jadi, jika kita berbicara tentang sesuatu seperti "aplikasi web real-time" maka SObjectizer dapat dengan mudah digunakan dalam sistem "real-time" tersebut.


Sistem embedded yang terkendala


SObjectizer bergantung pada pustaka standar C ++: std::thread digunakan untuk manajemen thread, std::atomic , std::mutex , std::condition_variable digunakan untuk sinkronisasi data, RTTI dan dynamic_cast digunakan insize SObjectizer (misalnya , std::type_index digunakan untuk identifikasi jenis pesan), pengecualian C ++ digunakan untuk pelaporan kesalahan.


Ini berarti bahwa SObjectizer tidak dapat digunakan di lingkungan di mana fasilitas perpustakaan standar tersebut tidak tersedia. Sebagai contoh, dalam pengembangan embedded embedded system di mana hanya bagian dari C ++ dan C ++ stdlib dapat digunakan.


Di mana SObjectizer digunakan di masa lalu?


Sekarang kami mencoba untuk berbicara singkat tentang beberapa kasus penggunaan penggunaan SObjectizer di masa lalu (dan tidak hanya di masa lalu). Sayangnya, ini bukan informasi lengkap karena ada beberapa masalah.


Pertama-tama, kita tidak tahu tentang semua penggunaan SObjectizer. SObjectizer adalah perangkat lunak gratis yang dapat digunakan bahkan dalam proyek berpemilik. Jadi beberapa orang hanya mendapatkan SObjectizer dan menggunakannya tanpa memberikan umpan balik kepada kami. Terkadang kami memperoleh beberapa informasi tentang penggunaan SObjectizer (tetapi tanpa detail), terkadang kami tidak tahu apa-apa.


Masalah kedua adalah izin untuk berbagi informasi tentang penggunaan SObjectizer dalam proyek tertentu. Kami telah menerima izin itu sangat jarang, dalam kebanyakan kasus pengguna SObjectizer tidak ingin membuka detail implementasi proyek mereka (kadang-kadang kami memahami alasannya, kadang tidak).


Kami mohon maaf atas fakta bahwa informasi yang diberikan terlihat sangat langka dan tidak berisi perincian. Meskipun demikian, ada beberapa contoh penggunaan SObjectizer:


  • Gerbang agregasi SMS / USSD yang menangani lebih dari 500 juta pesan per bulan;
  • bagian dari sistem yang melayani pembayaran online melalui ATM dari salah satu bank terbesar Rusia;
  • pemodelan simulasi proses ekonomi (sebagai bagian dari penelitian Ph.D.);
  • akuisisi data terdistribusi dan sistem analitik. Data dikumpulkan pada titik yang didistribusikan di seluruh dunia oleh perintah dari simpul pusat. MQTT digunakan sebagai transportasi untuk kontrol dan distribusi data yang diperoleh;
  • lingkungan pengujian untuk memeriksa sistem kontrol real-time untuk peralatan kereta api;
  • sistem kontrol otomatis untuk pemandangan teater. Rincian lebih lanjut dapat ditemukan di sini ;
  • komponen platform manajemen data dalam sistem periklanan online.

Rasa SObjectizer


Mari kita lihat beberapa contoh sederhana untuk mencicipi SObjectizer. Itu adalah contoh yang sangat sederhana yang, kami harap, tidak memerlukan penjelasan tambahan kecuali komentar dalam kode.


Contoh "Halo, Dunia" tradisional dalam gaya Model Aktor


Contoh paling sederhana dengan hanya satu agen yang bereaksi terhadap hello pesan dan menyelesaikan pekerjaannya:


 #include <so_5/all.hpp> // Message to be sent to an agent. struct hello { std::string greeting_; }; // Demo agent. class demo final : public so_5::agent_t { void on_hello(mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; // Now agent can finish its work. so_deregister_agent_coop_normally(); } public: // There is no need is a separate constructor. using so_5::agent_t::agent_t; // Preparation of agent to work inside SObjectizer. void so_define_agent() override { // Subscription to 'hello' message. so_subscribe_self().event(&demo::on_hello); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Make and register an instance of demo agent. auto mbox = env.introduce_coop([](so_5::coop_t & coop) { auto * a = coop.make_agent<demo>(); return a->so_direct_mbox(); }); // Send hello message to registered agent. so_5::send<hello>(mbox, "Hello, World!"); }); } 

Versi lain dari "Halo, Dunia" dengan agen dan model Terbitkan / Berlangganan


Contoh paling sederhana dengan beberapa agen, semuanya bereaksi terhadap instance yang sama dari pesan hello :


 #include <so_5/all.hpp> using namespace std::string_literals; // Message to be sent to an agent. struct hello { std::string greeting_; }; // Demo agent. class demo final : public so_5::agent_t { const std::string name_; void on_hello(mhood_t<hello> cmd) { std::cout << name_ << ": greeting received: " << cmd->greeting_ << std::endl; // Now agent can finish its work. so_deregister_agent_coop_normally(); } public: demo(context_t ctx, std::string name, so_5::mbox_t board) : agent_t{std::move(ctx)} , name_{std::move(name)} { // Create a subscription for hello message from board. so_subscribe(board).event(&demo::on_hello); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Mbox to be used for speading hello message. auto board = env.create_mbox(); // Create several agents in separate coops. for(const auto & n : {"Alice"s, "Bob"s, "Mike"s}) env.register_agent_as_coop(env.make_agent<demo>(n, board)); // Spread hello message to all subscribers. so_5::send<hello>(board, "Hello, World!"); }); } 

Jika kita menjalankan contoh itu, kita dapat menerima sesuatu seperti itu:


 Alice: greeting received: Hello, World! Bob: greeting received: Hello, World! Mike: greeting received: Hello, World! 

Contoh "Halo, Dunia" dalam gaya CSP


Mari kita lihat contoh SObjectizer tanpa aktor, cukup std::thread dan saluran CSP-like.


Versi yang sangat sederhana


Ini adalah versi yang sangat sederhana yang tidak terkecuali aman:


 #include <so_5/all.hpp> // Message to be sent to a channel. struct hello { std::string greeting_; }; void demo_thread_func(so_5::mchain_t ch) { // Wait while hello received. so_5::receive(so_5::from(ch).handle_n(1), [](so_5::mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; }); } int main() { // Run SObjectizer in a separate thread. so_5::wrapped_env_t sobj; // Channel to be used. auto ch = so_5::create_mchain(sobj); std::thread demo_thread{demo_thread_func, ch}; // Send a greeting. so_5::send<hello>(ch, "Hello, World!"); // Wait for demo thread. demo_thread.join(); } 

Versi yang lebih kuat, namun tetap sederhana


Ini adalah versi modifikasi dari contoh yang ditunjukkan di atas dengan tambahan keamanan pengecualian:


 #include <so_5/all.hpp> // Message to be sent to a channel. struct hello { std::string greeting_; }; void demo_thread_func(so_5::mchain_t ch) { // Wait while hello received. so_5::receive(so_5::from(ch).handle_n(1), [](so_5::mhood_t<hello> cmd) { std::cout << "Greeting received: " << cmd->greeting_ << std::endl; }); } int main() { // Run SObjectizer in a separate thread. so_5::wrapped_env_t sobj; // Demo thread. We need object now, but thread will be started later. std::thread demo_thread; // Auto-joiner for the demo thread. auto demo_joiner = so_5::auto_join(demo_thread); // Channel to be used. This channel will be automatically closed // in the case of an exception. so_5::mchain_master_handle_t ch_handle{ so_5::create_mchain(sobj), so_5::mchain_props::close_mode_t::retain_content }; // Now we can run demo thread. demo_thread = std::thread{demo_thread_func, *ch_handle}; // Send a greeting. so_5::send<hello>(*ch_handle, "Hello, World!"); // There is no need to wait for something explicitly. } 

Contoh HSM yang agak sederhana: blinking_led


Ini adalah contoh standar dari distribusi SObjectizer. Agen utama dari contoh ini adalah HSM yang dapat dijelaskan oleh statechart berikut:


statinkart blinking_led


Kode sumber contoh:


 #include <iostream> #include <so_5/all.hpp> class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off final : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1500}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { auto m = env.introduce_coop( []( so_5::coop_t & coop ) { auto led = coop.make_agent< blinking_led >(); return led->so_direct_mbox(); } ); auto pause = []( unsigned int v ) { std::this_thread::sleep_for( std::chrono::seconds{v} ); }; std::cout << "Turn blinking on for 10s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 10 ); std::cout << "Turn blinking off for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 5 ); std::cout << "Turn blinking on for 5s" << std::endl; so_5::send< blinking_led::turn_on_off >( m ); pause( 5 ); std::cout << "Stopping..." << std::endl; env.stop(); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; } 

Pengatur waktu, kontrol kelebihan untuk agen dan dispatcher active_obj


Kontrol overload adalah salah satu masalah utama bagi para aktor: antrian pesan untuk aktor biasanya tidak terbatas dan ini dapat menyebabkan pertumbuhan antrian yang tidak terkendali jika produsen pesan yang cepat mengirim pesan lebih cepat maka penerima dapat menanganinya. Contoh berikut menunjukkan fitur SObjectizer seperti batas pesan . Hal ini memungkinkan untuk membatasi jumlah pesan dalam antrian agen dan mempertahankan penerima dari pesan yang berlebihan.


Contoh ini juga menunjukkan penggunaan timer dalam bentuk pesan berkala. Ikatan agen ke dispatcher active_obj juga ditampilkan di sana. Mengikat ke operator itu berarti bahwa setiap agen dari koperasi akan bekerja pada utas pekerja sendiri (misalnya agen menjadi objek aktif).


 #include <so_5/all.hpp> using namespace std::chrono_literals; // Message to be sent to the consumer. struct task { int task_id_; }; // An agent for utilization of unhandled tasks. class trash_can final : public so_5::agent_t { public: // There is no need is a separate constructor. using so_5::agent_t::agent_t; // Preparation of agent to work inside SObjectizer. void so_define_agent() override { // Subscription to 'task' message. // Event-handler is specified in the form of a lambda-function. so_subscribe_self().event([](mhood_t<task> cmd) { std::cout << "unhandled task: " << cmd->task_id_ << std::endl; }); } }; // The consumer of 'task' messages. class consumer final : public so_5::agent_t { public: // We need the constructor. consumer(context_t ctx, so_5::mbox_t trash_mbox) : so_5::agent_t{ctx + // Only three 'task' messages can wait in the queue. limit_then_redirect<task>(3, // All other messages will go to that mbox. [trash_mbox]{ return trash_mbox; })} { // Define a reaction to incoming 'task' message. so_subscribe_self().event([](mhood_t<task> cmd) { std::cout << "handling task: " << cmd->task_id_ << std::endl; std::this_thread::sleep_for(75ms); }); } }; // The producer of 'test' messages. class producer final : public so_5::agent_t { const so_5::mbox_t dest_; so_5::timer_id_t task_timer_; int id_counter_{}; // Type of periodic signal to produce new 'test' message. struct generate_next final : public so_5::signal_t {}; void on_next(mhood_t<generate_next>) { // Produce a new 'task' message. so_5::send<task>(dest_, id_counter_); ++id_counter_; // Should the work be stopped? if(id_counter_ >= 10) so_deregister_agent_coop_normally(); } public: producer(context_t ctx, so_5::mbox_t dest) : so_5::agent_t{std::move(ctx)} , dest_{std::move(dest)} {} void so_define_agent() override { so_subscribe_self().event(&producer::on_next); } // This method will be automatically called by SObjectizer // when agent starts its work inside SObjectizer Environment. void so_evt_start() override { // Initiate a periodic message with no initial delay // and repetition every 25ms. task_timer_ = so_5::send_periodic<generate_next>(*this, 0ms, 25ms); } }; int main() { // Run SObjectizer instance. so_5::launch([](so_5::environment_t & env) { // Make and register coop with agents. // All agents will be bound to active_obj dispatcher and will // work on separate threads. env.introduce_coop( so_5::disp::active_obj::make_dispatcher(env).binder(), [](so_5::coop_t & coop) { auto * trash = coop.make_agent<trash_can>(); auto * handler = coop.make_agent<consumer>(trash->so_direct_mbox()); coop.make_agent<producer>(handler->so_direct_mbox()); }); }); } 

Jika kita menjalankan contoh itu, kita dapat melihat output berikut:


 handling task: 0 handling task: 1 unhandled task: 5 unhandled task: 6 handling task: 2 unhandled task: 8 unhandled task: 9 handling task: 3 handling task: 4 handling task: 7 

Output ini menunjukkan bahwa beberapa pesan yang tidak dapat masuk ke batas yang ditentukan ditolak dan dialihkan ke penerima lain.


Lebih banyak contoh


Contoh yang kurang lebih mirip dengan kode dari aplikasi kehidupan nyata dapat ditemukan di proyek demo Udang kami. Serangkaian contoh menarik lainnya dapat ditemukan dalam mini-seri ini tentang "masalah makan filsuf" klasik: bagian 1 dan bagian 2 . Dan, tentu saja, ada banyak contoh di SObjectizer itu sendiri .


Bagaimana dengan kinerjanya?


Ada jawaban yang sangat sederhana: lebih dari cukup untuk kita. SObjectizer dapat mendistribusikan jutaan pesan per detik, dan kecepatan sebenarnya tergantung pada jenis dispatcher yang digunakan, jenis pesan, profil beban, perangkat keras / OS / kompiler yang digunakan dan sebagainya. Dalam aplikasi nyata, kami biasanya hanya menggunakan sebagian kecil dari kecepatan SObjectizer.


Kinerja SObjectizer untuk tugas khusus Anda sangat tergantung pada tugas Anda, solusi khusus tugas itu, pada perangkat keras atau lingkungan virtual Anda, pada versi kompiler dan OS Anda. Jadi cara terbaik untuk menemukan jawaban untuk pertanyaan itu adalah dengan membuat tolok ukur sendiri yang akan spesifik untuk tugas Anda dan bereksperimen dengannya.


Jika Anda ingin angka dari beberapa tolok ukur sintetis maka ada beberapa program dalam folder test / so_5 / bench dari distribusi SObjectizer.


Catatan tentang perbandingan dengan alat yang berbeda


Kami berpikir bahwa permainan pembandingan membandingkan kecepatan berbagai alat adalah permainan pemasaran. Kami melakukan upaya di masa lalu tetapi dengan cepat menyadari bahwa itu hanya buang-buang waktu saja. Jadi kita tidak memainkan game itu sekarang. Kami menghabiskan waktu dan sumber daya kami hanya pada tolok ukur yang memungkinkan kami memeriksa tidak adanya penurunan kinerja, untuk menyelesaikan beberapa kasus sudut (seperti kinerja kotak MPMC dengan jumlah pelanggan yang besar atau kinerja agen dengan ratusan ribu pelanggan), untuk mempercepat beberapa operasi SObjectizer-spesifik (seperti pendaftaran / deregistrasi koperasi).


Jadi kami meninggalkan perbandingan kecepatan untuk mereka yang menyukai permainan itu dan punya waktu untuk memainkannya.


Mengapa SObjectizer terlihat persis seperti apa adanya?


Ada beberapa "kerangka aktor" untuk C ++, dan semuanya terlihat berbeda. Tampaknya ia memiliki beberapa alasan obyektif: setiap kerangka kerja memiliki fitur unik dan menargetkan berbagai tujuan. Selain itu, aktor dalam C ++ dapat diimplementasikan dengan sangat berbeda. Jadi pertanyaan utamanya bukanlah "mengapa kerangka X tidak terlihat seperti kerangka Y?", Tetapi "mengapa kerangka X memang terlihat seperti itu?"


Sekarang kita akan mencoba menjelaskan beberapa alasan di balik fitur-fitur SObjectizer utama secara singkat. Kami harap ini memungkinkan pemahaman yang lebih baik tentang kemampuan SObjectizer. Tetapi sebelum kita mulai perlu menyebutkan satu hal yang sangat penting: SObjectizer belum pernah menjadi eksperimen. Itu diciptakan untuk memecahkan kerja kehidupan nyata dan telah berevolusi berdasarkan pengalaman kehidupan nyata.


Agen adalah objek kelas yang berasal dari agent_t


Agen (alias aktor) dalam SObjectzer adalah objek kelas yang ditentukan pengguna yang harus diturunkan dari agent_t class khusus. Ini mungkin terlihat berlebihan dalam contoh mainan kecil, tetapi pengalaman kami menunjukkan bahwa pendekatan sangat menyederhanakan pengembangan perangkat lunak nyata di mana agen biasanya memiliki ukuran dalam beberapa ratus baris (Anda dapat melihat salah satu contoh di sini , tetapi posting blog ini ada di Rusia). Terkadang bahkan dalam beberapa ribu baris.


Pengalaman menunjukkan kepada kita bahwa agen sederhana dengan versi pertama dalam seratus baris menjadi jauh lebih gemuk dan kompleks dalam beberapa tahun mendatang evolusi. Jadi, setelah lima tahun Anda dapat menemukan monster dalam ribuan baris dengan puluhan metode.


Penggunaan kelas memungkinkan kita untuk mengelola kompleksitas agen. Kita bisa menggunakan warisan kelas. Dan kita bisa menggunakan kelas template juga. Ini adalah teknik yang sangat berguna yang sangat menyederhanakan pengembangan keluarga agen dengan logika serupa di dalamnya.


Pesan sebagai objek pengguna / kelas pengguna


Pesan di SObjectizer adalah objek struct atau kelas yang ditentukan pengguna. Setidaknya ada dua alasan untuk itu:


  • pengembangan SObjectizer-5 dimulai pada 2010 ketika C ++ 11 belum distandarisasi. Jadi pada awalnya, kami tidak dapat menggunakan fitur C ++ 11 seperti templat variadic dan std::tuple class. Satu-satunya pilihan yang kami miliki adalah penggunaan objek kelas yang diwarisi dari message_t kelas khusus. Sekarang tidak perlu untuk menurunkan jenis pesan dari message_t , tetapi SObjectizer membungkus objek pengguna ke objek yang di-message-message, di bawah kap;
  • isi pesan dapat dengan mudah diubah tanpa modifikasi tanda tangan penangan acara. Dan ada kontrol dari kompiler: jika Anda menghapus beberapa bidang dari pesan atau mengubah jenisnya maka kompiler akan memberi tahu Anda tentang akses yang salah ke bidang itu.

Penggunaan pesan sebagai objek juga memungkinkan untuk bekerja dengan pesan yang telah dialokasikan sebelumnya dan untuk menyimpan pesan yang diterima ke beberapa wadah dan mengirimnya kembali nanti.


Koperasi agen


Sekelompok agen mungkin adalah salah satu fitur unik dari SObjectizer. Koperasi adalah sekelompok agen yang harus ditambahkan dan dihapus dari SObjectizer dengan cara transaksi. Ini berarti bahwa jika sebuah koperasi berisi tiga agen maka semua agen tersebut harus ditambahkan ke SObjectizer dengan sukses atau tidak ada satupun dari mereka yang harus ditambahkan. Demikian pula, ketiga agen harus dihapus dari SObjectizer atau ketiga agen harus melanjutkan pekerjaan mereka.


Kebutuhan di dalam koperasi ditemukan segera setelah dimulainya kehidupan SObjectizer. Menjadi jelas bahwa agen akan dibuat oleh kelompok, bukan oleh satu contoh. Coops diciptakan untuk menyederhanakan kehidupan pengembang: tidak perlu mengontrol pembuatan agen berikutnya dan menghapus agen yang dibuat sebelumnya jika pembuatan agen baru gagal.


Sebuah koperasi juga dapat dilihat sebagai pengawas dalam mode semua-untuk-satu: jika agen dari koperasi gagal maka seluruh koperasi akan dihapus dari Lingkungan SObjectizer dan dimusnahkan (pengguna dapat bereaksi terhadap hal itu dan membuat kembali kandang lagi).


Kotak pesan


Kotak pesan adalah fitur unik lain milik SObjectizer. Pesan dalam SObjectizer dikirim ke kotak pesan (mbox), bukan ke agen secara langsung. Mungkin ada satu penerima di belakang mbox, atau mungkin ada satu juta pelanggan, atau tidak ada satu.


Mboxes memungkinkan kami untuk mendukung fungsionalitas dasar model Publish-Berlangganan. Mbox dapat dilihat sebagai MQ-broker dan jenis pesan dapat dilihat sebagai topik.


Mbox memungkinkan kami juga menerapkan berbagai bentuk pengiriman pesan yang menarik. Misalnya, ada mbox round-robin yang menyebarkan pesan antara pelanggan secara round-robin. Ada juga kotak penyimpanan yang menampung pesan yang terakhir dikirim dan mengirim ulang secara otomatis untuk setiap pelanggan baru. Ada juga pembungkus sederhana di sekitar libmosquitto yang memungkinkan untuk menggunakan MQTT sebagai transportasi untuk aplikasi terdistribusi.


Agen sebagai HSM


Agen di SObjectizer adalah mesin negara. Itu dari awal hanya karena SObjectizer memiliki akar di bidang SCADA, di mana mesin negara secara aktif digunakan. Tetapi dengan cepat menjadi jelas bahwa agen dalam bentuk mesin negara dapat berguna bahkan di ceruk yang berbeda (seperti aplikasi telekomunikasi dan keuangan).


Dukungan mesin keadaan hierarkis (mis. Penangan on_enter / on_exit, status bersarang, batas waktu dan sebagainya) ditambahkan setelah beberapa waktu menggunakan SObjectizer dalam produksi. Dan fitur ini membuat SObjectizer alat yang lebih kuat dan nyaman.


Penggunaan pengecualian C ++


Pengecualian C ++ digunakan dalam SObjectizer sebagai mekanisme pelaporan kesalahan utama. Terlepas dari kenyataan bahwa penggunaan pengecualian C ++ terkadang bisa mahal, kami memutuskan untuk menggunakan pengecualian alih-alih kode kesalahan.


Kami memiliki pengalaman negatif dengan kode kesalahan di SObjectizer-4, di mana pengecualian tidak digunakan. Ini menyebabkan ketidaktahuan kesalahan dalam kode aplikasi dan kadang-kadang tindakan penting tidak dilakukan karena ada kesalahan membuat koperasi baru atau mengirim pesan. Tetapi kesalahan ini diabaikan dan fakta itu ditemukan jauh kemudian.


Penggunaan pengecualian C ++ di SObjectizer-5 memungkinkan penulisan kode yang lebih benar dan kuat. Dalam kasus biasa, pengecualian dilemparkan oleh SObjectizer sangat jarang sehingga penggunaan pengecualian tidak berdampak negatif pada kinerja SObjectizer atau kinerja aplikasi yang ditulis di atas SObjectizer.


Tidak ada dukungan untuk aplikasi terdistribusi "out of box"


SObjectzer-5 tidak memiliki dukungan bawaan untuk aplikasi terdistribusi. Ini berarti bahwa SObjectizer mendistribusikan pesan hanya di dalam satu proses. Jika Anda perlu mengatur distribusi pesan antar-proses atau antar-catatan maka Anda harus mengintegrasikan beberapa jenis IPC dalam aplikasi Anda.


Ini bukan karena kami tidak dapat mengimplementasikan beberapa bentuk IPC di SObjectizer. Kami sudah memilikinya di SObjectizer-4. Dan karena kami memiliki pengalaman seperti itu, kami memutuskan untuk tidak melakukannya di SObjectizer-5. Kami belajar bahwa tidak ada satu jenis IPC yang sangat cocok untuk kondisi yang berbeda.


Jika Anda ingin memiliki komunikasi antar-simpul yang baik dalam aplikasi Anda, Anda harus memilih protokol dasar yang sesuai. Misalnya, jika Anda harus menyebarkan jutaan paket kecil dengan beberapa data yang berumur pendek (seperti distribusi pengukuran kondisi cuaca saat ini) maka Anda harus menggunakan satu IPC. Tetapi jika Anda harus mentransfer BLOB besar (seperti streaming video 4K / 8K atau arsip dengan data keuangan di dalamnya) maka Anda harus menggunakan jenis IPC lain.


Dan kami tidak berbicara tentang ketidakberdayaan dengan perangkat lunak yang ditulis dalam berbagai bahasa ...


Anda dapat percaya bahwa beberapa "kerangka kerja aktor" universal dapat memberi Anda IPC yang bagus untuk kondisi yang berbeda. Tetapi kita tahu bahwa itu hanya omong kosong pemasaran. Pengalaman kami menunjukkan kepada kita bahwa jauh lebih sederhana dan lebih aman untuk menambahkan IPC yang Anda butuhkan dalam aplikasi Anda kemudian mengandalkan ide, kebutuhan, dan pengetahuan penulis dari "kerangka aktor" pihak ke-3.


SObjectizer memungkinkan menggabungkan berbagai jenis IPC dalam bentuk mbox khusus. Jadi itu memungkinkan menyembunyikan fakta distribusi pesan melalui jaringan dari pengguna SObjectizer.


Alih-alih kesimpulannya


Kerangka kerja SObjectizer bukan yang besar, tapi itu bukan yang kecil. Jadi tidak mungkin untuk memberi pembaca kesan yang cukup mendalam tentang SObjectizer hanya dalam satu ikhtisar. Karena itu, kami mengundang Anda untuk melihat proyek SObjectizer.


SObjectizer sendiri hidup di GitHub . Ada Wiki proyek di GitHub dan kami sarankan untuk memulai dari SObjectizer 5.6 Dasar - dasar dan kemudian pergi ke artikel dari seri Mendalam. Bagi mereka yang ingin masuk lebih dalam, kami dapat merekomendasikan Mari kita lihat di bawah bagian sungkup SObjectizer .


Jika Anda memiliki pertanyaan, Anda dapat bertanya kepada kami di grup SObjectizer di grup Google.

Source: https://habr.com/ru/post/id458202/


All Articles