Merhaba yazılımperver dostlarım. Bu yazım ile birlikte artık elimizi kirletiyor ve boost kütüphanesini adam akıllı kullanmaya başlıyoruz. Buna da, asio kabiliyetler ile birlikte oldukça sık kullanılan bind’a hızlıca bakarak başlıyor olacağız. Sonra da, io_context sınıfına bakacağız. Önceki boost yazılarım için aşağıdaki bağlantılarıma göz atabilirsiniz:
Haftalık C++ 54 – Boost.Asio I – Giriş
Haftalık C++ 55 – Boost Asio II – Temeller ve Mimari
boost::bind
boost::bind, kısaca, farklı argüman tiplerini alan, fonksiyonlara geçiren ve bunun sıralaması konusunda da kolaylık sağlayan bir mekanizma sunmakta. Peki sadece fonksiyon mu? Hayır, tekil fonksiyon, sınıf metotları, fonksiyon nesneleri, fonksiyon işaretçilerini de bu bağlamda kullanabileceksiniz.
boost::bind, aslında daha önce std::bind1st() ve std::binds2nd() fonksiyonları ile sunulan bir takım özellikleri daha basitleştiren ve genelleştiren bir kullanım sunmaktadır. Bu arada, bu iki kabiliyet, C++98 ile birlikte gelmiş, boost.bind bunları daha kolay kullanılmasına sağlayan bir mekanizma sunmakta, zaten C++ 11 ile birlikte std::bind da <functional> başlık dosyası ile dahil edilmiş durumda (bu yazıda bunların farklarına girmeyeceğim ama bire bir aynı olmasa da farklar mevcut, meraklıları std::bind vs boost::bind sonuçlarına göz atabilir).
Hemen bir örnek ile konuya atlayalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <boost/bind.hpp> #include <vector> #include <algorithm> #include <iostream> void PrintSum(std::ostream *os, int i, int j) { *os << "Toplam: " << i + j << '\n'; } class PrintClass{ public: void PrintSum(std::ostream *os, int i, int j) const { *os << "Toplam: " << i + j << '\n'; } }; int main() { std::vector<int> v{1, 3, 2}; // Tek fonksiyon kullanimi // Her bir elemana bes ekleyelim std::for_each(v.begin(), v.end(), boost::bind(PrintSum, &std::cout, _1, 5)); // Sinif uyesi fonksiyon kullanimi // Her bir elemani kendisi ile toplayalim PrintClass printObject; std::for_each(v.begin(), v.end(), boost::bind(&PrintClass::PrintSum, &printObject, &std::cout, _1, _1)); } |
Örnek kod sanırım sizlere, boost::bind’ın kullanımına ilişkin bir fikir verecektir. Temelde, verilen farklı çağrılabilir fonksiyon, fonksiyon nesnelerine girdileri jenerik bir şekilde iletilebildiğini görebileceksiniz.
Yukarıdaki kullanım yanında, std::function benzeri, boost::function kullanımında da, boost::bind kullanılabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> #include <boost/bind.hpp> #include <boost/function.hpp> void PrintDifference(int i, int j) { std::cout << "Fark: " << i - j << '\n'; } int main() { // Asagidaki gibi bir kullanim oldugu gibi, yoruma alinan auto'lu satir da kullanilabilir const boost::function<void(int, int)> fun1 = boost::bind(&PrintDifference, _1, _2); // const auto fun = boost::bind(&PrintSum, _1, _2); // Arguman sirasi bu noktada onemli fun1(5, 6); // Arguman sirasinin yerini degistirebilirsiniz const auto fun2 = boost::bind(&PrintDifference, _2, _1); fun2(5, 6); // Beklenenden fazla girilen argumanlar da goz ardi edilmektedir fun2(8, 3, 2, 5, 6, 1, 7); return 0; } |
Şimdi biraz asio’ya taraflarına yaklaşabiliriz.
boost::asio::io_context
boost::bind’tan sonra artık asio’nun temel motivasyonu olan asenkron çağrılara göz atmanın zamanı geldi. Bunun da temelinde io_context sınıfı bulunmaktadır (proactor örüntüsü gerçekleyen). İnternet üzerindeki örneklerde, boost::asio::io_service sınıfının da kullanıldığını görebilirsiniz ama yeni boost sürümleri ile birlikte io_context sınıfının kullanılması tavsiye edilmektedir. Bu sınıf kısaca, işletim sisteminin sunduğu I/O alt yapılarını soyutlayarak, kendisine atanan işi asenkron (veya istenen şekilde) gerçekleştirilmesi için gerekli fonksiyonaliteyi sunar. Çeşitli kaynaklarda “executor” olarak da kullanılabiliyor.
Bu nesneye (io_context) öğreneceğimiz ilk API run()
‘dır. Bu API ile, io_context’in olay işleme döngüsünü başlatıyoruz. Bu döngü, işlenecek herhangi bir iş kalmayana kadar ya da ilgili io_context nesnesi durdurulana kadar, çalıştırıldığı thread’i bloklar. Burada bir diğer güzel özellik (hatta ölçeklendirmek isteyeceğiniz uygulamalarınızda ihtiyaç da duyacağınız) de, bu nesnenin run() API’sini farklı threadlerden de çağırabileceğinizdir. Bu sayede, bir nevi thread havuzu da oluşturmuş oluyoruz. Bir diğer ifade ile, verdiğiniz işleri eritecek birden fazla thread olmuş oluyor. İlave bir girdi veya kontrol yapmadığınız müddetçe hangi threadin bu amaçla kullanılacağını bilemiyoruz (gerek de olmayacak şekilde uygulamalarımızı tasarlayabiliriz). Bunun yanında, run() (ve benzeri run_one, run_until, vb.) API’lerini aynı thread’ten birden fazla kez çağırmamalıyız. Aşağıda, kullanıma ilişkin temel bir şablon bulabilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 |
boost::asio::io_context io_context; ... for (;;) { try { io_context.run(); break; } catch (my_exception& e) { // Varsa hatalar, onlar ile ilgilenelim } } |
Diğer yaşam döngüsü API’leri için https://www.boost.org/doc/libs/1_81_0/doc/html/boost_asio/reference/io_context.html sayfasına göz atabilirsiniz. Burada hızlıca poll() API’sinden de bahsedebilirim. run()’dan farklı olarak, bu API uygulamayı bekletmeden sadece hazır kotarıcıları/işlerin işlenmesini sağlar. Ayrıca, run ve poll API’lerinin tek işlemi eritmeye yönelik API’leri de bulunmaktadır. Bunları uygulamanızın ihtiyacına göre kullanabilirsiniz.
Bir diğer sınıf ise executor_work_guard (eski sürümlerde work olarak da kullanılıyordu). Bu sınıfın kısaca görevi de, io_context’in run() API’sini çağırdıktan sonra, iş kalmayınca dönmeyip devam etmesini sağlar. Bu kullanımda, durdurmak için ise, io_context::stop() API’sini ya da, executor_work_guard’un reset() API’sini kullanabilirsiniz. Aşağıda da buna ilişkin şablon bir kod bulabilirsiniz:
1 2 3 4 5 |
boost::asio::io_context io_context; boost::asio::executor_work_guard<boost::asio::io_context::executor_type> = boost::asio::make_work_guard(io_context); ... work.reset(); |
Evet, ilgili io_context sınıfını oluşturduk, başlattık ya da durdurduk. Şimdi sıra geldi, bu sınıfı kullanarak asenkron işleri yaptırmaya.
Bu amaçla kullanabileceğimiz temel iki API bulunmaktadır. Bunlar,post()
vedispatch()
API’leridir.
post():
Verilen işi kuyruğa koyup, io_context nesnesi tarafından, ilgili thread içerisinde koşturulması (yani çağrılan thread’ten değil) için kullanılan API’dir,dispatch():
Bir önceki API’den farklı olarak, io_context’in hemen ilgili işi koşturması ve bunu da run(), poll() ya da benzeri API’lerin çağrıldığı threadten yapması için kullanılan API’dir ve kuyruğa koymaz.
Tahmin edebileceğiniz, üzere bu iki API yanında bir çok diğer API’de bulunmakta fakat temel işlevleri göstermek ve kullanmak adına bunların yeterli olacağı kanısındayım.
Vermiş olduğumuz bu işler bitene kadar daha önce bahsettiğim run() API’si dönüş yapmıyor olacak ve bütün bu işlerin tamamlanmasını bekliyor olacak.
Şimdi bu öğrendiklerimizi, kapsamlı bir örnek (https://github.com/yazilimperver/asio_adventure/tree/master/boost_bind_example) ile inceleyelim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
boost::mutex global_stream_lock; // Bu ornek icin tek bir thread olusturuyoruz void WorkerThread(boost::shared_ptr<boost::asio::io_context> ioctx) { global_stream_lock.lock(); std::cout << "Thread Basladi." << " Thread: " << boost::lexical_cast<std::string>(boost::this_thread::get_id()) << "\n"; global_stream_lock.unlock(); ioctx->run(); global_stream_lock.lock(); std::cout << "Thread Bitti.\n"; global_stream_lock.unlock(); } // dispatch() API'si icin kullanilacak // Cagiran thread icerisinde cagriliyor ve hemen cagrilir // Bu sebeple ilk olarak bu fonksiyonlar çağrılıyor olacaklar. void Dispatch(int i) { global_stream_lock.lock(); std::cout << "dispath() fonksiyonu => i = " << i << std::endl; global_stream_lock.unlock(); } // post() API'si icin kullanilacak // Context'in threadi icerisinde cagriliyor ve her zaman kuyruga eklenir void Post(int i) { global_stream_lock.lock(); std::cout << "post() fonksiyonu => i = " << i << std::endl; global_stream_lock.unlock(); } // 5'er adet, iki farkli API araciligi ile is verelim void Running(boost::shared_ptr<boost::asio::io_context> ioctx) { for (int x = 0; x < 5; ++x ) { // Iste burada boost::bind ile ilgili fonksiyonlara, dongu numarasini geciriyoruz ioctx->dispatch(boost::bind(&Dispatch, x)); ioctx->post(boost::bind(&Post, x)); boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); } } int main(void) { // 0 - Temel io_context sinifimizi olusturalim boost::shared_ptr<boost::asio::io_context> io_ctx(new boost::asio::io_context); boost::asio::executor_work_guard<boost::asio::io_context::executor_type> worker = boost::asio::make_work_guard(*io_ctx.get()); global_stream_lock.lock(); std::cout << "Butun isler bittigi zaman uygulama sonlanacaktir." << std::endl; global_stream_lock.unlock(); boost::thread_group threads; // Burada, boost::bind'i bir threade, thread fonksiyonunu gecirmek icin kullaniyoruz threads.create_thread(boost::bind(&WorkerThread, io_ctx)); // Burada da, asio'ya bir is veriyor ve context nesnesini geciriyoruz io_ctx->post(boost::bind(&Running, io_ctx)); worker.reset(); threads.join_all(); global_stream_lock.lock(); std::cout << "Butun isler tamamlandi!" << std::endl; global_stream_lock.unlock(); return 0; } |
Bu örnekteki önemli noktaları, numaralandırarak üzerinden geçiyor olalım:
- Yukarıda, io_context nesnesinin iş kalmayınca (run()) çıkmaması için kullanımına değinmiştik,
- Örnek boyunca, komut satırına yapılan çıktıların birbirine karışmaması için mutex kullanıyoruz,
- io_context için bir thread oluşturup emrine veriyoruz (ileride çoklu thread örneklerine de bakıyor olacağız),
- io_context nesnesi için post/dispatch API’lerini nasıl kullanacağımıza, boost::bind üzerinden bakalım. Burada post() ile geçirdiğimiz için, oluşturulan thread içerisinde bu koşturuluyor olacak,
- Bu fonksiyon işletilirken, içerisinde post/dispatch için beşer adet çağrı yapıp davranışı görmemizi sağlayacak,
- dispatch() için kullanılan fonksiyon (şu an sadece ne olduğunu basıyor),
- post() için kullanılan fonksiyon (şu an sadece ne olduğunu basıyor),
- Artık işler bittiği için, io_context nesnesini bekletmemize gerek yok.
Evet sevgili dostlar, kodun da üzerinden geçtiğimize göre, çıktıya bir göz atalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
❯ ./boost_bind_example Butun isler bittigi zaman uygulama sonlanacaktir. Thread Basladi. Thread: 7f839925c700 dispath() fonksiyonu => i = 0 dispath() fonksiyonu => i = 1 dispath() fonksiyonu => i = 2 dispath() fonksiyonu => i = 3 dispath() fonksiyonu => i = 4 post() fonksiyonu => i = 0 post() fonksiyonu => i = 1 post() fonksiyonu => i = 2 post() fonksiyonu => i = 3 post() fonksiyonu => i = 4 Thread Bitti. Butun isler tamamlandi! |
Öncelikli olarak, dispatch() API’lerinin eritildiğine dikkat edelim. Bu arada, bu ve sonraki yazılılarım için kullanacağım örneklere aşağıdaki repo’dan ulaşabilirsiniz:
https://github.com/yazilimperver/asio_adventure
Bir sonraki yazımda görüşmek dileğiyle, bol kodlu günler diliyorum.