C++11 – std::thread

C++ programlama dilinin en büyük eksikliklerinden biri de built-in concurrency desteğiydi. Aslında bu sorunu uzun zamandır C programcıları posix thread kütüphanesi ile çözüyordu. C++ programcıları ise boost kütüphanesinin bir parçası olan thread kütüphanesini kullandılar. C++11 ile artık dile multi-threading desteği boost kütüphanesinin boost::thread kütüphanesinin büyük bir kısmı miras alınarak dilin standardına eklendi.

C++ konferanslarında dinlediğim, boost::thread in yazarlarından Anthony Wiliams’ ın kitabını okuyarak edindiğim bazı bilgileri bu blogta türkçe kaynak olması amacıyla paylaşmayı uygun gördüm. Olabildiğince örneklendirerek anlamayı ve kullanım hatalarını en aza indirmeyi amaçlıyorum.

O zaman ilk olarak geleneksel ‘Hello World’ ü thread ile oluşturalım.

#include <iostream>
#include <thread>



using namespace std;

void threadFunc()
{
    cout << "Hello from Thread" << endl;
}


int main(int argc, char** argv) 
{
    thread th (threadFunc);
    
    
    th.join();

    return 0;
}

std isim alanı altında bulunan thread sınıfı görüldüğü üzere ctor fonksiyonuna thread de çalıştırılması düşünülen fonksiyonun adresi geçilerek inşaa edilir. Daha sonra ise ‘th’ isimli yeni bir thread oluşturan ana thread oluşturduğu threadin işinin bitmesini thread sınıfındaki ‘join’ fonksiyonunu çağırarak sağlar. Bu noktada ana thread bloke olur ve ‘th’ isimli threading işi bitene kadar bekler. Bu konuya ikinci bölümde daha detaylı ele alacağız.

Sadece fonksiyon göstericileri ile degil istenirse C++11 ile gelen özelliklerden biri olan lambda ifadeleri ve Functor (fonksiyon çağırma operatörü overload edilmiş nesne) nesneler ile de ctor çağırılabilir.

#include <iostream>
#include <thread>

using namespace std;

struct SFtor
{
    void operator ()()
    {
        cout << "Hello from Ftor" << endl;
    }
};

int main()
{
    SFtor sf;
    
    
   thread th([]() {
       
       cout << "Hello from Lambda" << endl;
   });
   
   thread th1(sf);
   
   
   th.join();
   th1.join();
   
   return 0;
}

Performans odaklı program yazıyorsanız functor kullanmanız lehinize olur inline olarak değerlendirilmelerinden ötürü daha hızlı işler. Tabi bu mesele standart olarak değerlendirmek mümkün değil, derleyici bağımlıdır.

Threadlere Parametre Geçmek
Thread sınıfı değişken parametreli şablon fonksiyonlarına (variadic templates) güzel bir örnektir. Bu sayede thread değişkenine geçilen fonksiyonunun parametresini thread sınıfının ctor ına 2. parametre olarak geçebilirsiniz.

#include <iostream>
#include <thread>

using namespace std;

struct SFtor
{
    void operator ()(string const &s)
    {
        cout << "Hello from Ftor " << s <<endl;
    }
};

void threadFunc(int a)
{
    cout << "Hello from traditional func " << a << endl;
}

int main()
{
    SFtor sf;
    
    
   thread th(threadFunc, 5);
   
   thread th1(sf, "cpp istanbul");
   
   
   th.join();
   th1.join();
   
   return 0;
}

Örneklerde görüldüğü gibi thread sınıfının ctorının ilk parametresinden sonra girilen argümanlar çağırılacak olan fonksiyon nesnesine parametre olarak geçiliyor. Yalnız burada dikkat edilmesi gereken noktalardan biri parametreler thread fonksiyonuna geçilirken kopyalanarak aktarılır. Eğer referans olarak geçmek istersek std::ref veya std::cref fonksiyonları kullanılır.

#include <iostream>
#include <thread>

using namespace std;


void threadFunc(int &a)
{
    cout << "thread: " << a << endl;
    a++;
}

int main()
{
   int a = 5;
    
   thread th(threadFunc, ref(a));
   
   th.join();
    
   cout << "main thread: " << a << endl;
    
   return 0;
}

Yukarıdaki örnekte de değişken threade referans olarak geçilerek fonksiyon gövdesinde bir arttırlıyor. Kodu çalıştırdığımızda sonuç olarak ekrana:

thread: 5
main thread: 6

yazacaktır.

Thread fonksiyonuna lambda ifadeleri, Functorlar ve normal fonksiyonlar haricinde bir sınıfın üye fonksiyonunu da geçebilirsiniz. Eğer daha önce std::bind fonksiyonunu kullandıysanız benzer bir sentaksa sahip olduğunu hemen farkedeceksiniz.

Örneğin, ilk parametreye üye fonksiyon adresi daha sonra ilgili sınıfın nesnesi daha sonra ise sırayla fonksiyona eğer varsa parametreler geçilir.

#include <iostream>
#include <thread>

using namespace std;

struct SMemFunc
{
    void foo(string &s)
    {
        s+= " istanbul";
    }

};



int main()
{
    SMemFunc mf;
    string s("cpp");
    
    cout << "before join s: " << s << endl;
    
    thread th(&SMemFunc::foo, mf, ref(s));
    
    th.join();
    
    cout << "after join s: " << s << endl;
     
   return 0;
}

Yukarıdaki örnek çalıştığında referans yoluyla SMemFunc sınıfına geçilen ‘s’ nesnesi fonksiyonda sonuna ‘istanbul’ eklenir. Kısacası çalıştırılan program ekrana:

before join s: cpp
after join s: cpp istanbul

yazdırılır.

Devam edecek…