Haftalık C++ 56 – Boost Asio III – boost::bind, boost::io_context

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:

Ö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.

Ş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  ‘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:

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:

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, ve API’leridir.

  • 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,
  • 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:

Bu örnekteki önemli noktaları, numaralandırarak üzerinden geçiyor olalım:

  1. Yukarıda, io_context nesnesinin iş kalmayınca (run()) çıkmaması için kullanımına değinmiştik,
  2. Örnek boyunca, komut satırına yapılan çıktıların birbirine karışmaması için mutex kullanıyoruz,
  3. io_context için bir thread oluşturup emrine veriyoruz (ileride çoklu thread örneklerine de bakıyor olacağız),
  4. 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,
  5. Bu fonksiyon işletilirken, içerisinde post/dispatch için beşer adet çağrı yapıp davranışı görmemizi sağlayacak,
  6. dispatch() için kullanılan fonksiyon (şu an sadece ne olduğunu basıyor),
  7. post() için kullanılan fonksiyon (şu an sadece ne olduğunu basıyor),
  8. 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:

Ö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.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.