C++17 ile std::shared_mutex Kullanımı ve Avantajları

C++17 ile std::shared_mutex Kullanımı ve Avantajları

C++17 ile birlikte gelen std::shared_mutex, çoklu iş parçacıklarının erişimini kolaylaştırarak performansı artırır. Detayları keşfedin.

Paylas

Giriş

C++17 ile tanıtılan std::shared_mutex, çoklu iş parçacığı uygulamalarında veri erişimini optimize etmek için önemli bir araçtır. Çoklu iş parçacıkları ile veri paylaşımı, modern yazılım geliştirmede yaygın bir ihtiyaçtır. Ancak, bu tür durumlarda performans sorunları yaşanabilir. std::shared_mutex, okuyucu-yazar senaryolarında verimliliği artırarak bu sorunların üstesinden gelmeye yardımcı olur. Bu makalede, std::mutex ile karşılaştırarak std::shared_mutex kullanımının avantajlarını inceleyeceğiz.

Temel Bir Thread-Güvenli Sayıcı Örneği

Başlangıç olarak, std::mutex ile basit bir sayıcı sınıfı oluşturalım:

#include <mutex>

class Counter {
public:
    int get() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return value_;
    }

    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
    }
private:
    mutable std::mutex mutex_;
    int value_{0};
};

Bu kod parçası, birden fazla iş parçacığının güvenli bir şekilde erişim sağlamasına olanak tanır. Ancak burada önemli bir sınırlama bulunmaktadır: Tüm erişimler birbirini dışlar. Yani, aynı anda yalnızca bir iş parçacığı get() veya increment() fonksiyonlarını çağırabilir. Bu durum, okuma ve yazma işlemleri arasında önemli bir engel oluşturur.

std::mutex ile Performans Sınırlamaları

Gerçek dünya uygulamalarında, paylaşılan veriler çoğunlukla okunur ve nadiren güncellenir. Örneğin:

  • Yapılandırma verileri

  • İstatistikler ve metrikler

std::mutex kullanıldığında, birden fazla okuma işlemi birbirini engeller. Bu, ölçeklenebilirliği sınırlar ve mevcut paralelliği boşa harcar. Aşağıdaki kod örneğinde, okuma işlemleri yazma işlemlerine göre çok daha fazladır:

int main() {
    Counter counter;
    std::vector<std::jthread> threads;

    threads.emplace_back([&counter] {
        for (int i = 0; i < 10; ++i) {
            counter.increment();
            std::this_thread::sleep_for(15ms);
        }
    });

    for (int i = 0; i < 4; ++i) {
        threads.emplace_back([&counter, i] {
            for (int j = 0; j < 10; ++j) {
                std::println("reader {} sees {}", i, counter.get());
                std::this_thread::sleep_for(10ms);
            }
        });
    }
}

Bu kodda, yazıcı iş parçacığı sayacı artırırken, okuyucu iş parçacıkları durmadan aynı değeri görmektedir. Bu durum, performans problemlerini ortaya çıkarır.

std::shared_mutex Nedir?

std::shared_mutex, okuyucu-yazar türünde bir mutex'tir. İki kilitleme modu destekler:

  • Paylaşılan sahiplik: Birden fazla iş parçacığı aynı anda kilidi tutabilir.

  • Özel sahiplik: Yalnızca bir iş parçacığı kilidi tutabilir.

Bu özellik, std::shared_mutex'i okuma ağırlıklı veri yapıları için ideal hale getirir. Şimdi, sayıcı örneğimizi std::shared_mutex ile güncelleyelim:

#include <shared_mutex>
#include <mutex> // for locks

class Counter {
public:
    int get() const {
        std::shared_lock lock(mutex_);
        return value_;
    }

    void increment() {
        std::unique_lock lock(mutex_);
        ++value_;
    }

private:
    mutable std::shared_mutex mutex_;
    int value_{0};
};

Bu değişiklikle birlikte, birçok iş parçacığı aynı anda get() fonksiyonunu çağırabilirken, yazma işlemleri tamamen korunur. Bu, okuma ölçeklenebilirliğini büyük ölçüde artırır.

Performans Kazançlarını Ölçmek

Şimdi, std::mutex ve std::shared_mutex arasındaki performans farkını ölçelim. Aşağıdaki kod, her iki yapı için okuma ve yazma sürelerini karşılaştıracaktır:

#include <chrono>
#include <mutex>
#include <print>
#include <shared_mutex>
#include <thread>
#include <vector>

using namespace std::chrono_literals;

constexpr auto ReadWork = 2ms;
constexpr auto WriteWork = 1ms;
constexpr auto GapWork = 1ms;

// --- std::mutex ile versiyon ---
class CounterMutex {
public:
    int get() const {
        std::lock_guard<std::mutex> lock(mutex_);
        std::this_thread::sleep_for(ReadWork);
        return value_;
    }

    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
        std::this_thread::sleep_for(WriteWork);
    }
private:
    mutable std::mutex mutex_;
    int value_{0};
};

// --- std::shared_mutex ile versiyon ---
class CounterSharedMutex {
public:
    int get() const {
        std::shared_lock lock(mutex_);
        std::this_thread::sleep_for(ReadWork);
        return value_;
    }

    void increment() {
        std::unique_lock lock(mutex_);
        ++value_;
        std::this_thread::sleep_for(WriteWork);
    }
private:
    mutable std::shared_mutex mutex_;
    int value_{0};
};

void run_test(const char* label, auto& counter) {
    constexpr int kReaders = 4;
    constexpr int kReadsPerReader = 30;
    constexpr int kWrites = 20;

    const auto start = std::chrono::steady_clock::now();

    {
        std::vector<std::jthread> threads;
        threads.reserve(1 + kReaders);

        // Yazıcı
        threads.emplace_back([&] {
            for (int i = 0; i < kWrites; ++i) {
                counter.increment();
                std::this_thread::sleep_for(GapWork);
            }
        });

        // Okuyucular
        for (int id = 0; id < kReaders; ++id) {
            threads.emplace_back([&counter] {
                for (int i = 0; i < kReadsPerReader; ++i) {
                    (void)counter.get();
                    std::this_thread::sleep_for(GapWork);
                }
            });
        }
    }

    const auto end = std::chrono::steady_clock::now();
    const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    std::println("{}: {} ms", label, ms.count());
}

int main() {
    std::println("hardware_concurrency: {}", std::thread::hardware_concurrency());

    CounterMutex counter_mutex;
    CounterSharedMutex counter_shared;

    run_test("std::mutex", counter_mutex);
    run_test("std::shared_mutex", counter_shared);
}

Bu kod, std::mutex ve std::shared_mutex için karşılaştırmalı süreleri gösterir. Yapılan testlerde std::shared_mutex ile elde edilen performans, std::mutex a göre önemli ölçüde daha iyidir. std::mutex ile toplam çalışma süresi 285 ms iken, std::shared_mutex ile bu süre 102 ms'ye düşmektedir. Bu, okuma işlemlerinin eşzamanlı olarak gerçekleştirilmesinin sağladığı büyük bir avantajdır.

Sonuç

C++17 ile birlikte gelen std::shared_mutex, okuma-yazma senaryolarında verimliliği artıran önemli bir araçtır. Çok sayıda okuyucu iş parçacığının aynı anda veri okumasına olanak tanırken, yazma işlemlerini de güvenli bir şekilde yönetir. Ülkemizdeki yazılım geliştiricilerinin, bu yeni özellikleri projelerinde kullanmaları önerilir. Gelecekte, daha fazla paralel işleme ihtiyaç duyuldukça, std::shared_mutex gibi yapılar daha da önem kazanacaktır.

Etiketler

Can Berk Erdem

Yazar

Can Berk Erdem

TechPusula yazarı. Teknoloji ve dijital dönüşüm üzerine içerikler üretmektedir.

Tüm yazıları gör

Yorumlar

Henüz yorum yapılmamış. İlk yorumu siz yapın!

Yorum Yaz

0/2000

İlginizi Çekebilir

Tüm yazılar