Haftalık C++ 55 – Boost Asio II – Temeller ve Mimari

Merhaba sevgili yazılımperver dostlarım, Boost.Asio yazılarımıza devam ediyorum. Bir önceki yazımda, Boost.Asio’yu nasıl indirebileceğimiz, hedefleri ve çok kısa bir giriş yapmıştık. Bu yazımda ise temel kavramlarına, kabiliyetlerine, kullanımına ve örnek bir uygulamaya bakıyor olacağız. Bir kaç yazı sonrasında da, burada edindiğimiz kabiliyetleri, uEngine4’e entegre ediyor olacağız ve maceramıza oradan devam edeceğiz. Bir önceki yazımı da buraya bırakayım:

Haftalık C++ 54 – Boost.Asio I – Giriş

Haydi Boost.Asio’ya daha yakından bakalım. Bu yazımın bir çok yerinde https://www.boost.org/doc/libs/1_81_0/doc/html/boost_asio/overview/core.html sayfasındakı içerikleri sizler ile paylaşıyor olacağım. Bir kaç farklı kaynaktan bulduğum ve faydalı olduğuna inandığım hususları da buraya ekliyor olacağım. Bu sebeple, daha detaylı bilgiler için ilgili sayfalara erişebilirsiniz.

Proactor Tasarım Örüntüsü – Giriş

Bir önceki yazımda da bahsettiğim gibi Boost.Asio hem senkron hem de asenkron operasyonları desteklemektedir. Asenkron operasyonlar için ise “Proactor” tasarım örüntüsünü kullanmaktadır (Bu örüntünün  orjinal dokümanına ulaşmak için POSA2 sayfasına ulaşabilirsiniz.

Boost sayfasında, proactor tasarım örüntüsüne ilişkin detaylı bir aktarım yapılmakta, fakat onun öncesinde kısa bir özet yapmak istiyorum sizlere.

Peki, bu örüntü bize ne sağlıyor? Özetle bize, multithreaded programlama ile uğraşmadan, olayların asenkron bir şekilde kotarılmasına yönelik bir çözüm sunuyor ve yazılımın olay-tabanlı bir şekilde tasarlanmasına yönlendirmektedir.

Peki bunu nasıl yapıyor? Aşağıda da gösterildiği üzere, kısaca sonucuna ihtiyaç duyduğumuz hesaplamayı direk çağırmak yerine, bu işi başka bir thread’e aktarıp, iş bitince, çağıranı haberdar edecek şekilde çalışmakta.

Burada, ana thread tarafından bir iş göre kuyruğuna aktarılır aktarılmaz, eğer boş bir işçi thread tarafından ele alınarak kotarılır.

Bu noktada, bu tasarım örüntüsünün daha önce bahsettiğimiz aktif nesne ve reactor örüntüsü ile benzerlik içerdiğini görüyor olacaksınız (reactor’e daha önce değinmedim).

Öncelikle aktif nesne ile karşılaştırdığımızda, her iki örüntü de, aslında “invocation” ile “execution”‘ı bir birinden bağımsız hale getirmesi anlamında benzerler fakat, Proactor örüntüsü, aktif nesne  yaklaşımına nazaran daha merkezi bir yaklaşım izlemekte ve işletim sisteminin sunduğu alt seviye servisleri de kullanarak, diğerine nazaran daha performanslı bir çözüm sunduğu kaynaklar kısmına da eklediğim dokümanlarda ifade edilmekte.

Şimdi de, proactor ve reactor örüntülerinin farkına bir göz atalım. Her iki yaklaşımda, olayların (event) kotarılması için eşzamanlı çalışan yazılımlar için sunulmaktadır. Temel olarak, reactor örüntüsünde, asenkron gerçekleştirilecek olan operasyon, gerçekleştirilmeye hazır olduğunda, istemci haberdar edilir, proactorde ise ilgili oeprasyon gerçekleştirilir ve tamamlanması sonrasında istemci haberdar edilir. Soket haberleşmesi üzerinden bunu açıklayacak olursak, reactor örüntüsünde, soketten veri okumak isteyen istemci, veri okunmaya hazır olayını alır ve sonrasın okuma işlemini gerçekleştirir. Proactor örüntüsünde ise, istemci okuma isteğini iletir ve okuma isteği gerçekleşince bir olay ile haberdar edilir.

Aşağıda bir Web sunucusuna gelen isteğin reactor ve proactor yaklaşımlarına göre nasıl ele alındığı gösterilmekte (Reactor soldaki, Proactor ise sağdaki). Detaylar için  https://www.dre.vanderbilt.edu/~schmidt/PDF/proactor.pdf dokümanına göz atabilirsiniz:

Proactor Tasarım Örüntüsü – Boost

Şimdi de, boost sayfasında verilen, asio tarafından da benimsenen daha detaylı açıklamara bir göz atalım.

 

  • Asenkron Operasyon (“Asynchronous Operation”)
    • Asenkron olarak yürütülebilecek operasyon olarak tanımlanır, ör soketten veri okuma veya yazma.
  • Asenkron Operasyon İşlemcisi (“Asynchronous Operation Processor”)
    • Asenkron operasyonları gerçekleştirip, tamamlanma bilgilerini ilgili tamamlanma olay kuyruğuna ekler.
  • Tamamlanma Olay Kuyruğu (“Completion Event Queue”)
    • Tamamlanma olaylarının, asenkron olay çoğullayıcısı (Asynchronous event demultiplexer) tarafından tüketilmeden önce tutulacağı kuyruk.
  • Tamamlanma Kotarıcısı (“Completion Handler”)
    • Bir asenkron operasyon sonucu işleyen bileşen, bir diğer ifade ile fonksiyon nesnelerini temsil ediyor ve genelde boost::bind ile sunulmakta.
  • Asenkron Olay Çoğullayıcısı  (“Asynchronous Event Demultiplexer”)
    • Tamamlanma olay kuyruğuna yeni bir olay gelene kadar bloklanan ve gelince de istemciye dönüş sağlar.
  • Proactor
    • Asenkron olay çoğullayıcısını olayları kuyruktan çekmesi için tetikler ve tamamlanma kotarıcılarını çağırır.  Aslında io_context sınıfının temelde temsil ettiği işlev bu.
  • İstemci (“Initiator”)
    • Asenkron operasyonu tetikleyen, çağıran istemcidir, bunu da asenkron operasyon işlemcisi aracılığı ile yapar.

Yukarıda da belirttiğim sayfa içerisinde, Boost.asio’nun bu işlerini nasıl gerçekleştirdiğini bulabilirsiniz. Burada bu konulara girmeyeceğim ama meraklı okuyucularım ilgili kaynaklara bir göz atabilirler.

Bu yaklaşımın avantajları aşağıdaki gibi sıralanabilir:

  • Taşınabilirlik (“Portability”)
    • Bir çok işletim sistemi doğal asenkron I/O API’si sunulmakta ve yüksek performanslı ağ uygulamaları için bu daha çok tercih edilmektedir. Boost.asio da bunu POSIX ve Windows için sizlerden soyutlamakta.
  • Eşzamanlı Çalışmayı, Thread’lerden Ayırma (” Decoupling threading from concurrency”)
    • Uygulamanın kendisinin bir çok thread oluşturarak, uzun sürecek işleri bunlara yaptırmasından, uygulamayı kurtarmakta (burada dikkat edilmesi gereken, hiç thread oluşturulmaması değil, bunun kontrolünü boost.asio’nun üstlenmesi),
  • Performans ve Ölçeklendirme (“Performance and Scalability”)
    • Her bir bağlantıya bir thread her zaman performans anlamında bir fark yaratmayabilir hatta yavaşlatabilir (context-switch, senkronizasyon ve CPU’lar arası veri değişimi),
  • Basiteleştirilmiş Uygulama Senkronizasyon
    • Burada uygulamaya ilişin operasyonlar, tamamlanma kotarıcıları üzerinden yapıldığı için senkronizasyon hususlarının önüne geçilmektedir,

Elbette, bu yaklaşıma ilişkin bir takım dezavantajlar da sıralanmış durumda:

  • Program Karmaşıklığı
    • Asenkron mekanizmalara kullanarak uygulama geliştirme her zaman kolay olmayabilir ve hata ayıklama zorlaşabilir,
  • Bellek Kullanımı
    • Asenkron operasyonlar devam ederken, bu operasyonlar için alanlar ayrılmalı ve korunmalıdır (reaktör örüntüsünde bu ihtiyaç bulunmuyor)

Kaynaklar

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.