C++ (17+) için thread-safe generic message channel kütüphanesi


    Github Repository: https://github.com/leventkaragol/libcpp-channel

    linux windows

    Amaç

    Gelişmiş uygulamalarda, kodun farklı bölümleri arasında mesaj alışverişine sıklıkla ihtiyaç duyulur. Bu kod blokları doğrudan birbirine bağlı olmadığında ve ayrı iş parçacıklarında çalıştığında, özellikle birden fazla mesaj üreticisi ve tüketicisi varsa, aralarındaki iletişimi yönetmek daha zor hale gelir. Bu gibi durumlarda kanallar, bağımsız kod blokları arasında iş parçacığı güvenli bir şekilde iletişimi kolaylaştırmak için kullanılan yapılardır. Görünüşte olaylara benzeseler de, çalışma mantıkları daha çok mesaj kuyruklarına benzer.

    Kanallara ihtiyaç duyabileceğiniz birkaç durum şunlardır:

    • Thread-safe veri paylaşımı gereksinimi: Birden fazla thread arasında thread-safe iletişimi sağlamanız gerektiğinde
    • Üreticileri ve tüketicileri ayırmak: Veri üreticilerini ve tüketicilerini birbirinden ayırarak bağımsız olarak çalışmalarına olanak sağlamak
    • Birden fazla üretici ve tüketiciyi yönetmek: Veri üreten ve tüketen birden fazla iş parçacığınız olduğunda ve etkileşimlerini yönetmek için bir mekanizmaya ihtiyaç duyduğunuzda
    • FIFO mesaj kuyruklarını uygulamak: Mesajların alındıkları sırayla işlendiğinden emin olunmak istendiğinde
    • Senkronizasyonu basitleştirmek: Uygulamanızdaki senkronizasyon mantığını basitleştirerek karmaşık kilitleme mekanizmalarından kaçınmak istediğinizde
    • Paralelliği geliştirmek: Görevleri birden fazla thread arasında dağıtarak paralelliği ve performansı arttırma gereksinimi olduğunda
    • Gerçek zamanlı verileri işlemek: Thread'ler arasında gerçek zamanlı verilerin hızlı ve güvenli bir şekilde iletilmesi
    • Ölçeklenebilir sistemler tasarlamak: Sistemin diğer bölümlerini etkilemeden bileşenlerin eklenebileceği veya çıkarılabileceği ölçeklenebilir sistemlerin oluşturulması

    Projeye Nasıl Eklenir?

    Bu header-only bir kütüphanedir. Yani aslında tek yapmanız gereken src klasöründeki libcpp-channel.hpp dosyasını projenize eklemek ve #include ile kullanmaya başlamaktır.

    Kullanım örneklerini Github Repository'deki "examples" klasöründe bulabilirsiniz. Aşağıda örnek bir CMakeLists.txt dosyası içeriği de bulabilirsiniz.

    cmake_minimum_required(VERSION 3.14)
    
    project(myProject)
    
    add_executable(myProject main.cpp libcpp-channel.hpp)
    

    Nasıl Kullanılır? (Tek Üretici ve Tek Tüketici)

    En kolay kullanım örneği olarak, tek bir üreticiden mesaj gönderip, tek bir tüketiciden mesaj almak aşağıdaki şekilde yapılabilir.

    #include "libcpp-channel.hpp"
    #include <iostream>
    #include <thread>
    
    using namespace lklibs;
    
    void produce(Channel<std::string>::Producer producer)
    {
        auto i = 0;
    
        while (true)
        {
            i++;
    
            // Tüketiciye string mesaj gönderme
            producer.send("Message " + std::to_string(i));
    
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
    
    void consume(Channel<std::string>::Consumer consumer)
    {
        while (true)
        {
            // Üreticiden gelen mesajı alma
            auto message = consumer.receive();
    
            if (message.has_value())
            {
                std::cout << "Consumer Received: " << message.value() << std::endl;
            }
        }
    }
    
    int main()
    {
        // Bir kanal oluşturuyoruz
        Channel<std::string> channel;
    
        // Üretici ve tüketici nesnelerini alıyoruz
        auto producer = channel.getProducer();
        auto consumer = channel.getConsumer();
    
        // Üretici nesnesini ilk Thread'e geçiyoruz
        std::thread produce_thread(::produce, std::move(producer));
    
        // Tüketici nesnesini ikinci Thread'e geçiyoruz
        std::thread consume_thread(::consume, std::move(consumer));
    
        produce_thread.join();
        consume_thread.join();
    
        return 0;
    }
    

    Nasıl Kullanılır? (Tek Üretici ve Çoklu Tüketici)

    Aşağıdaki örnekte, tek bir üreticiden gönderilen mesaj aynı anda iki farklı tüketici tarafından alınmaktadır.

    #include "libcpp-channel.hpp"
    #include <iostream>
    #include <thread>
    
    using namespace lklibs;
    
    void produce(Channel<std::string>::Producer producer)
    {
        auto i = 0;
    
        while (true)
        {
            i++;
    
            // Tüm tüketicilere string mesaj gönderme
            producer.send("Message " + std::to_string(i));
    
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
    
    void consume(Channel<std::string>::Consumer consumer, const std::string& name)
    {
        while (true)
        {
            // Üreticiden gelen mesajı alma
            auto message = consumer.receive();
    
            if (message.has_value())
            {
                std::cout << name << " Received: " << message.value() << std::endl;
            }
        }
    }
    
    int main()
    {
        // Bir kanal oluşturuyoruz
        Channel<std::string> channel;
    
        // Üretici ve tüketici nesnelerini alıyoruz
        auto producer = channel.getProducer();
        auto consumer1 = channel.getConsumer();
        auto consumer2 = channel.getConsumer();
    
        // Üretici nesnesini ilk Thread'e geçiyoruz
        std::thread produce_thread(::produce, std::move(producer));
    
        // Tüketici nesnelerini diğer Thread'lere geçiyoruz
        std::thread consume1_thread(::consume, std::move(consumer1), "Consumer1");
        std::thread consume2_thread(::consume, std::move(consumer2), "Consumer2");
    
        produce_thread.join();
        consume1_thread.join();
        consume2_thread.join();
    
        return 0;
    }
    

    Nasıl Kullanılır? (Çoklu Üretici ve Tek Tüketici)

    Farklı bir kullanım örneği olarak, iki farklı üreticinin aynı kanala mesaj göndermesi ve bu mesajları tek bir tüketicinin alması örneğini verebiliriz.

    #include "libcpp-channel.hpp"
    #include <iostream>
    #include <thread>
    
    using namespace lklibs;
    
    void produce(Channel<std::string>::Producer producer, const std::string& name)
    {
        auto i = 0;
    
        while (true)
        {
            i++;
    
            // Tüketiciye string mesaj gönderme
            producer.send(name + " Message " + std::to_string(i));
    
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
    
    void consume(Channel<std::string>::Consumer consumer)
    {
        while (true)
        {
            // Üreticiden gelen mesajı alma
            auto message = consumer.receive();
    
            if (message.has_value())
            {
                std::cout << "Consumer Received: " << message.value() << std::endl;
            }
        }
    }
    
    int main()
    {
        // Bir kanal oluşturuyoruz
        Channel<std::string> channel;
    
        // Üretici ve tüketici nesnelerini alıyoruz
        auto producer1 = channel.getProducer();
        auto producer2 = channel.getProducer();
        auto consumer = channel.getConsumer();
    
        // Üretici nesnelerini kendi Thread'lerine geçiyoruz
        std::thread produce1_thread(::produce, std::move(producer1), "Producer1");
        std::thread produce2_thread(::produce, std::move(producer2), "Producer2");
    
        // Tüketici nesnesini kendi Thread'ine geçiyoruz
        std::thread consume_thread(::consume, std::move(consumer));
    
        produce1_thread.join();
        produce2_thread.join();
        consume_thread.join();
    
        return 0;
    }
    

    Nasıl Kullanılır? (Çoklu Üretici ve Çoklu Tüketici)

    Aşağıdaki örnekte, iki farklı üretici birbirinden bağımsız olarak aynı kanala mesaj gönderirken, iki farklı tüketici bu mesajları birbirinden bağımsız ve eş zamanlı olarak almaktadır.

    #include "libcpp-channel.hpp"
    #include <iostream>
    #include <thread>
    
    using namespace lklibs;
    
    void produce(Channel<std::string>::Producer producer, const std::string& name)
    {
        auto i = 0;
    
        while (true)
        {
            i++;
    
            // Tüketiciye string mesaj gönderme
            producer.send(name + " Message " + std::to_string(i));
    
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }
    }
    
    void consume(Channel<std::string>::Consumer consumer, const std::string& name)
    {
        while (true)
        {
            // Üreticiden gelen mesajı alma
            auto message = consumer.receive();
    
            if (message.has_value())
            {
                std::cout << name << " Received: " << message.value() << std::endl;
            }
        }
    }
    
    int main()
    {
        // Bir kanal oluşturuyoruz
        Channel<std::string> channel;
    
        // Üretici ve tüketici nesnelerini alıyoruz
        auto producer1 = channel.getProducer();
        auto producer2 = channel.getProducer();
        auto consumer1 = channel.getConsumer();
        auto consumer2 = channel.getConsumer();
    
        // Üretici nesnelerini kendi Thread'lerine geçiyoruz
        std::thread produce1_thread(::produce, std::move(producer1), "Producer1");
        std::thread produce2_thread(::produce, std::move(producer2), "Producer2");
    
        // Tüketici nesnelerini kendi Thread'lerine geçiyoruz
        std::thread consume1_thread(::consume, std::move(consumer1), "Consumer1");
        std::thread consume2_thread(::consume, std::move(consumer2), "Consumer2");
    
        produce1_thread.join();
        produce2_thread.join();
        consume1_thread.join();
        consume2_thread.join();
    
        return 0;
    }
    

    Semantik Versiyonlama

    Kütüphanenin sürümlemesi, geleneksel semantik versiyonlama kullanılarak yapılır. Buna göre, MAJOR.MINOR.PATCH biçiminde yapılan sürümlemede;

    PATCH: Olası hata düzeltmeleri ve iyileştirmeleri içerir. Bunu mutlaka edinmek istersiniz.

    MINOR: Geriye dönük uyumluluk sayesinde eklenen ek işlevler. Muhtemelen bunu edinmek istersiniz, zararı olmaz.

    MAJOR: Geriye dönük uyumluluğu bozan ek işlevler. Nelerin değiştiğini anlamanız ve muhtemelen kendi kodunuzda değişiklik yapmanız gerekecek. Böyle bir şey yayınlarsam, geçiş için gereken değişiklikleri kesinlikle dokümana eklerim.

    Lisans

    Proje MIT lisansı ile lisanslanmıştır. Ticari kullanıma uygundur. Tam metni aşağıda bulabilirsiniz.

    MIT License
    
    Copyright (c) 2024 Levent KARAGÖL
    
    Permission is hereby granted, free of charge, to any person obtaining a copy of this
    software and associated documentation files (the "Software"), to deal in the Software
    without restriction, including without limitation the rights to use, copy, modify,
    merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all copies
    or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
    

    İletişim

    Kütüphaneyle ilgili sorun yaşıyorsanız, lütfen GitHub'da bir konu açın. Lütfen isteğinizi, sorununuzu veya sorunuzu olabildiğince ayrıntılı bir şekilde açıklayın ve derleyicinizin, işletim sisteminizin ve kullandığınız kütüphanenin sürümünü de belirtin. Yeni bir konu açmadan önce, lütfen konunun daha önce kapatılmış konularda bulunmadığından emin olun.

    Yazar: Levent KARAGÖL

    Makaleyi Paylaş: