C++ Thread Nedir? Kullanımı
C++ 11 ile birlikte C++ diline eklenen ve paralel programlama yapmaya imkan veren thread kullanımı ve yönetimi ile ilgili bilgiler yer alıyor.
Paralel programlama nedir?
Aynı anda birden fazla işlem yapmaya imkan veren programlama yöntemine paralel programlama denir.
Paralel programlama yöntemleri yapısına göre çeşitli bölümlere ayrılsa da multiprocessing ve multithread olarak ikiye ayrılabilir.
Multiprocessing veya çoklu işlem işletim sistemleri tarafında ele alınır ve yönetilir.
Çoklu işlem sayesinde aynı anda hem müzik dinleyip hem de oyun oynama gibi işlemleri yapmamıza olanak sağlar.
Multiprocessing yönetiminde boru hattı (pipeline), eş zamanlılık (synchronous), eş zamansızlık (asynchronous) gibi teknikler kullanılır.
Multithread veya çoklu iplik gibi değişik bir anlamı olan paralel programlama tekniğidir.
Multithread her oluşturulan işlem tarafından oluşturulabilen ve kullanılabilen ana işlemin alt işlemleridir.
Multiprocessing ve Multithread arasındaki en önemli fark multiprocessing oluşturulurken işlemin diğer işlemlerden etkilenmemesi için kendine ait ayrı bir bellek alanı olmasıdır.
Multithread ise bir işlemin altına yer alan ayrı işlemler olarak ifade edilir ve ayrı bir bellek alanı ayrımı yoktur.
Neden multithread?
Multiprocessing tekniğine göre paralel programlama yöntemi, multithread yöntemine göre daha yavaştır.
Çünkü her multiprocessing işlemi başlatıldığında işletim sistemi tarafından işlem için ayrı bellek alanı ayrılır.
Ayrı bellek alanın yapılması ve işlemin yüklenmesi zaman alacağından multithread yöntemi kullanmak faydalı olacaktır.
Thread nedir?
Her bir işlemin altında çalışan alt işlemlere thread adı verilir.
İşlem tarafından yeni bir thread oluşturmak multiprocessing göre daha kolaydır.
Ancak thread yönetiminin iyi yapılması gerekir.
C++ thread
Modern C++ öncesi thread işlemleri için pthread, openmp, mpi, charm++ gibi çeşitli kütüphaneler ile thread yönetimi yapılmaktaydı.
C++ 11 ile birlikte gelen thread sınıfı dilin standardı olmuştur.
Thread kullanımı
Modern C++ ile birlikte thread kullanımı için thread başlık dosyasını eklenmesi yeterlidir.
#include <iostream>
#include <thread>
void yazdir()
{
std::cout << "Thread id: " << std::this_thread::get_id() << std::endl;
std::cout << "Yusuf Sezer" << std::endl;
}
int main()
{
std::thread alt_islem(yazdir);
alt_islem.join();
std::cout << "Thread id: " << std::this_thread::get_id() << std::endl;
return 0;
}
Sınıf thread yönetimi için get_id, join, joinable, detach, hardware_concurrency gibi metotlara sahiptir.
Sınıfın kurucu metodu thread tarafından kullanılan lamda fonksiyonu, sınıf fonksiyon operatörü ve fonksiyon alabilir.
Örnekte yazdir ve main olmak üzere iki thread yer alır.
Örnekte thread alt_islem ile yeni bir thread oluşturulmuş ve join metodu ile main threadına bağlanmıştır.
Buradan her bir işlem başlatıldığında main fonksiyonu ile bir thread başlatıldığı çıkartılabilir.
Sınıf içinde yer alan join metodu alt thread işleminin bitmesini bekler thread işleminin ayrı olarak ele alınması istendiğinde detach metodu kullanılır.
Ancak işlemin ayrı ele alınmasında oluşturulan thread main threadından sonra biterse bazı kaynaklara (stdin, stdout vb.) erişemeyecektir.
#include <iostream>
#include <thread>
void yazdir()
{
std::string adi;
std::cout << "Adınız: ";
std::cin >> adi;
std::cout << adi;
}
int main()
{
std::thread alt_islem(yazdir);
alt_islem.detach();
return 0;
}
Ayrılan bir işlem joinable metodu ile kontrol edilerek tekrar ana işlem ile birleştirilip birleştirilmediğine bakılabilir.
Parametre gönderimi için kurucu sınıfa ayrıca parametrelerin yazılması yeterli olacaktır.
#include <iostream>
#include <string>
#include <thread>
void yazdir(std::string_view t_mesaj)
{
std::cout << t_mesaj << std::endl;
}
int main()
{
std::string adi = "Yusuf Sezer";
std::thread alt_islem(yazdir, adi);
alt_islem.join();
return 0;
}
Gönderilen parametrede değişiklik yapılmak istenirse ref veya move ile gönderilmesi gerekir.
Ayrıca C++ 11 ile gelen atomic türlerinin kullanımı faydalı olacaktır.
Thread yönetimi
İşletim sistemi bir işlem başlatıldığında işlem tarafından kullanılmak üzere okuma (stdin), yazdırma (stdout) gibi çeşitli kaynakları ayırır.
Ancak birden fazla thread tek bir kaynağı aynı anda kullandığında beklenmeyen durumlar ortaya çıkar.
#include <iostream>
#include <thread>
void yazdir()
{
for (int i = 0; i < 10; i++)
std::cout << "Merhaba ben yazdir thread" << std::endl;
}
int main()
{
std::thread alt_islem(yazdir);
for (int i = 0; i < 10; i++)
std::cout << "Merhaba ben main thread" << std::endl;
alt_islem.join();
return 0;
}
Aynı kaynağın kullanıldığı durumlarda kaynağın bir süre diğer threadlara kapatılması gerekir.
Sınırlı kaynağın diğer threadlara kapatılması için mutex sınıfı kullanılır.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex_kilitleme;
void yazdir()
{
for (int i = 0; i < 10; i++)
{
mutex_kilitleme.lock();
std::cout << "Merhaba ben yazdir thread" << std::endl;
mutex_kilitleme.unlock();
}
}
int main()
{
std::thread alt_islem(yazdir);
for (int i = 0; i < 10; i++)
{
mutex_kilitleme.lock();
std::cout << "Merhaba ben main thread" << std::endl;
mutex_kilitleme.unlock(); // unutulursa
}
alt_islem.join();
return 0;
}
Ancak mutex lock metodu ile kilitlenen kaynak unlock ile tekrar serbest bırakılmazsa beklenmeyen durumlar (deadlock) ortaya çıkar.
Bu durumun önüne geçmek için ihtiyaca göre lock_guard, unique_lock, shared_lock, scoped_lock sınıflarının kullanılması yeterli olacaktır.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex_kilitleme;
void yazdir()
{
for (int i = 0; i < 10; i++)
{
std::lock_guard<std::mutex> kilitleme(mutex_kilitleme);
std::cout << "Merhaba ben yazdir thread" << std::endl;
}
}
int main()
{
std::thread alt_islem(yazdir);
for (int i = 0; i < 10; i++)
{
std::lock_guard<std::mutex> kilitleme(mutex_kilitleme);
std::cout << "Merhaba ben main thread" << std::endl;
}
alt_islem.join();
return 0;
}
Thread yönetimi zor peki neden multithread kullanılıyor.
Aslında multithread yöntemi ekran yazdırma, dosya yazma gibi sadece tek kaynağın olduğu işlemler için pek kullanılmaz.
Sayısal işlemlerin yapıldığı sayısal hesaplama, kriptoloji, görüntü işleme, soket programlama gibi işlemlerde hesaplamaların yapılması için kullanılır.
Ancak bu alanda kullanılabilmesi içinde geliştirilen uygulamaların parçalara ayrılabiliyor olması gerekir.
Çünkü bir thread başka bir threadın sonucunu beklerse bu sıradan bir işlem olacaktır.
Örneğin; Görüntü işleme esnasında işlem eşit parçalara bölünerek yapılırsa performans gözle görülür fark ortaya çıkaracaktır.
Yine kriptoloji işlemlerinde sıklıkla kullanılan asal sayı hesaplama işlemini düşünürsek 1 ila 1000 arası hesaplamayı bir thread 1000 ila 2000 arasını başka bir thread gibi parçalara bölmek performans açısından fark ortaya çıkaracaktır.
Ayrıca hardware_concurrency metodu ile önerilen thread sayısına göre işlem yapmak faydalı olacaktır.
Önerilen sayı aşıldığında ayrıca başka bir işlem başlatılarak daha da performanslı uygulamalar geliştirilebilir.
Modern C++ yazılarıma buradan ulaşabilirsiniz.
Hayırlı günler dilerim.