Teşekkürler Wordpress

Daha önce wordpressten kaynaklanan sentaks renklendirme hatalarından sıkıldığımız belirtmiştim. Bu sebepten ötürü artık bloğumuza direkt olarak cppturkey.com veya cppturkey.org siteleri üzerinden ulaşabilirsiniz. cppturkiye.wordpress.com artık güncellenmeyecektir.

Advertisements

C++11 – Bellek modeli ve atomik türler

C++ programlama dili yeni gelen standartlarla beraber belirsiz durum (undefined behaviour) diyebileceğimiz durumları en aza indirmeyi amaçladığını daha önceki yazılarda az çok değinmiştim. C++11’ le gelen en büyük yeniliklerden biri de multi-thread kod yazıldığında memory ordering veya cpu ordering gibi durumlardan kaynaklabilecek belirsiz durumları en aza indirgemeyi amaçlayan standart bir bellek modeli sunmasıdır.

Performans kaygısıyla derleyici veya işlemciniz sizin adınıza bazı değişiklikler yapabilir. Yani aslında tam olarak sizin yazdığınız kodun assembly karşılığını elde edemeyebilirsiniz. Örneğin,

int count = 5;
while(count)
   cout << "Beast is Back!" << endl;

 

Yukarıdaki koda baktığımızda öncelikle bellekte bir değişken yaratılması ve onun register’a yüklenip işlemci tarafından okunmasını beklemekteyiz. Ama aslında olan derleyicinin bu koda bakıp döngüyü while(true) şekline dönüştürerek çalışma zamanında performans kazanımını sağlar. Tabii ki derleyicinin en iyileme (optimization) seçeneklerinin açık olduğunu farzediyorum. Yukarıdaki kodu gcc 7.3 ile derlendiğinde iki farklı durumda ortaya çıkan işlemci komutları aşağıdaki gibidir.

/// varsayılan şekilde derlendiğinde
https://godbolt.org/g/LnsuZ7

/// -O3 ile derlendiğinde
https://godbolt.org/g/RiCUkU

Threadler arasında sağladığı bölünmezlik garantisi ile bellekte tutarlılık (consistency) sağlanmasına yardımcı olan std::atomic şablon sınıfıdır. Yani diyelim ki bir threadA int türünden bir a değişkenini okurken o sırada threadB tam bu okuma işlemi sırasında a değişkeninin bellekteki değerini değiştirebilir. Bu da tam da yarış durumudur (race condition). Eğer değişkenimiz atomic olarak tanımlansaydı okuma veya yazma işlemi bitmeden bölünmemesi garanti altında olacaktı.

   std::atomic <int> a = 0;  // ekrana 200000 olarak yazılması garanti altındadır.
    // int a = 0;  ekrana ne yazılacağı meçhul, belirsizlik durumu (undefined bahviour)

    std::thread writeThread( [&] () {
                                  for(int i = 0; i < 100000; i++) {
                                        a++;
                                  }
                              }
    );

    std::thread readThread( [&] () {
                                 for(int i = 0; i < 100000; i++) {
                                        a++;
                                 }
                             }
    );

    writeThread.join();
    readThread.join();

    cout << a << endl;

 

atomic şablon sınıfı türünden değişkenlerin erişimi(load) ve yazımı(store) sırasında bellek erişim biçimiyle beraber threadler arasında bellek paylaşımlarını yönetebileceğimiz özellikler getirdi modern C++. Bu özellikler ve getirdiği standartlarla beraber mutex, condition variable gibi özelliklerle elde ettiğimiz threadler arası senkronizasyonu elde etme imkanı sağlıyor hem de daha az bir maliyetle.

memory_order_seq_cst

Inter-thread değişkenlerin tamamı eğer ‘memory_order_seq_cst’ parametresiyle sıralanmışsa tüm threadlerin üzerinde anlaştığı global bir sıralama vardır. Tüm load ve store operasyonları da kendi aralarında da sıralıdır.
Örneğin,

atomic x, y;

Thread 1                             Thread 2
x.store(10);                      cout << y.load() << endl;
y.store(20);                      cout << x.load() << endl;

Yukarıdaki sıralamaya göre y değişkenini load ettiğimizde x değişkenininde store edilmesi garanti altındadır. Kısaca ekrana 20 ve 10 yazılabilir. Ne yazılamaz dersek 20, 0 yazılma ihtimali yoktur çünkü 'memory_order_seq_cst' sıralı tutarlılığı(sequential concsistency) garanti eder. Ayrıca her iki threadde de store ve load operasyonları sıralıdır. Yani bellekte x store edilmeden y store edilmez aynı şekilde load işleci için de geçerlidir. Unutmadan ekleyelim varsayılan bellek sıralama seçeneğidir.

memory_order_relaxed:

Atomic olmayan bir değişkenle yapılan sıralamalar gibidir. Herhangi bir bellek bariyeri (memory barrier) söz konusu değildir. Relax çalışır :slight_smile:. Yalnız hiçbir sıralama garantisi yoktur dersek de doğru olmaz tıpkı single thread uygulamalardaki gibi aynı thread içerisindeki load ve store operasyonlarında happens-before sıralaması vardır. Fakat load operasyonları store operasyonlarını rassal bir sıralama da görür. Yukarıdaki örneğe dönersek, 20-10, 20-0, 0-0, 0-10 gibi tüm ihtimaller olasıdır.

memory_order_acquire – memory_order_release:

memory_order_seq_cst daki gibi üzerinde anlaşılan global bir sıralama yoktur ama memory_order_relaxed sıralamasındaki gibi de değildir. Aynı thread içerisinde yine happens-before ilişkisiyle beraber acquire-release operasyonları arasında da synchronize-with ilişkisi vardır. Aslında bir anlamda memory_order_seq_cst in daha hafif halidir.
atomic x, y;

Thread 1 Thread2
x.store(10, memory_order_release); cout<<y.load(memory_order_acquire) << endl;
y.store(20, memory_order_release); cout <<x.load(memory_order_acquire) <<endl;

Burda release ve acquire operasyonları ikili (pairwise) olarak senkronize olarak çalışır. Eğer herhangi bir şekilde bu sekronizasyon kurulmadıysa relaxed olarak çalışır.

İhtiyacım olan single producer-single consumer lock-free queueyu şuradaki örneği alarak ihtiyaçlarım doğrultusunda geliştirdim. Fena da olmadı.

Yaptığım testlerde mutex ile implemente edilen versiyonu ile lock-free versiyonu arasında ciddi performans farkı var. Tabi bu fark ringbuffer kaç kez tur attığıyla orantılı olarak büyüyor. Kasıtlı olarak rakam vermiyorum ki herkes kendi bilgisayarında çalıştırıp farkı görsün.

Locksız versiyon:

template 
class GenericRingBuffer
{
public :

    GenericRingBuffer()
    {
        m_write.store(0, std::memory_order::memory_order_relaxed);
        m_read.store(0, std::memory_order::memory_order_relaxed);
    }

    void push(T val)
    {
        while(!try_push(val));
    }

    void pop(T *val)
    {
        while(!try_pop(val));
    }

    void pop_bulk(T *dst, size_t len)
    {
        while(!try_pop_bulk(dst, len));

    }

    void push_bulk(T *src, size_t len)
    {
        while(!try_push_bulk(src, len));
    }

private:

    bool try_push(T val)
    {
        const auto currentWHead = m_write.load(std::memory_order_relaxed);
        const auto nextWHead = currentWHead + 1;
        if (currentWHead - m_read.load(std::memory_order_acquire) = currentRHead) {
            if (currentWHead -  currentRHead < len)
                return false;
        }

        else if (currentWHead < currentRHead){
            if ((m_size - currentRHead) + currentWHead < len)
                return false;
        }

        size_t nextRHead = (currentRHead + len) % m_size;
        if (nextRHead  currentRHead) {
            if ((m_size - currentWHead) + currentRHead < len)
                return false;
        }

        else if (currentWHead < currentRHead) {
            if (currentRHead -  currentWHead < len)
                return false;
        }

        const auto nextWHead = (currentWHead + len) % m_size;
        if (nextWHead < currentWHead) {
            memcpy(m_buffer + currentWHead, src, (m_size - currentWHead) * sizeof(T));

            memcpy(m_buffer, src + (m_size - currentWHead),
                   nextWHead);
        }

        else
            memcpy(m_buffer + currentWHead, src, len);

        m_write.store(nextWHead, std::memory_order_release);
        return true;
    }

private :
    char cachePad_1[64];
    std::atomic<size_t> m_write;
    char cachePad_2[64];
    std::atomic<size_t> m_read;
    T m_buffer[m_size];
};

 

Mutexli versiyon ise;

 


template 
class GenericRingBuffer
{
public :

    GenericRingBuffer() 
    {
        m_write = 0;
        m_read = 0;
    }

    void clear()
    {
        std::lock_guard lock(mtx);
        m_read = 0;
        m_write = 0;

    }

    bool push(T val)
    {
        std::lock_guard lock(mtx);
        const auto nextWHead = m_write + 1;
        if (m_write - m_read <= m_size - 1) {
            m_buffer[m_write % m_size] = val;
            m_write = nextWHead;
            return true;
        }

        return false;
    }

    bool pop(T *val)
    {
        std::lock_guard lock(mtx);
        const auto nextRHead = m_read + 1;

        if (m_read == m_write) {
            return false;
        }

        *val = m_buffer[m_read % m_size];
        m_read = nextRHead;

        return true;
    }

    bool pop_bulk(T *dst, size_t len)
    {
        std::lock_guard lock(mtx);

        if (m_write >= m_read) {
            if (m_write -  m_read < len)
                return false;
        }

        else if (m_write < m_read){
            if ((m_size - m_read) + m_write < len)
                return false;
        }

        size_t nextRHead = (m_read + len) % m_size;
        if (nextRHead < m_read) {
            memcpy(dst, m_buffer + m_read, (m_size - m_read) * sizeof(T));
            memcpy(dst + (m_size - m_read), m_buffer, nextRHead * sizeof(T));

        }

        else {
            memcpy(dst, m_buffer + m_read, len);
        }

        m_read = nextRHead;

        return true;

    }

    bool push_bulk(T *src, size_t len)
    {
        std::lock_guard lock(mtx);

        if (m_write > m_read) {
            if ((m_size - m_write) + m_read < len)
                return false;
        }

        else if (m_write < m_read) {
            if (m_read -  m_write < len)
                return false;
        }

        const auto nextWHead = (m_write + len) % m_size;

        if (nextWHead < m_write) {
            memcpy(m_buffer + m_write, src, (m_size - m_write) * sizeof(T));

            memcpy(m_buffer, src + (m_size - m_write),
                   nextWHead);
        }

        else
            memcpy(m_buffer + m_write, src, len);

        m_write = nextWHead;
        return true;

    }


private :
    size_t m_write;
    size_t m_read;
    T m_buffer[m_size];
    std::mutex mtx;
};

int main (int argc, char** argv)
{
    auto start = chrono::steady_clock::now();

    char a[10] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'};

    GenericRingBuffer grb;

    std::thread write_thread( [&] () {
                                  for(int i = 0; i < 2000; i++) {
                                      grb.push_bulk(a, 10);
                                  }
                              }
    );

    std::thread read_thread( [&] () {
                                 for(int i = 0; i < 2000; i++) {
                                     char b[10];
                                     grb.pop_bulk(b, 10);
                                 }
                             }
    );

    write_thread.join();
    read_thread.join();


    auto end = chrono::steady_clock::now();
    cout << chrono::duration  (end - start).count() << " ms " << endl;

    return 0;
}

Şunu da şuraya bırakayım:
https://github.com/rigtorp/awesome-lockfree

Bu arada bu wordpress uzun zamandır canımı sıkıyor sentaks highlighting konusunda bu yazının daha yakışıklı versiyonuna burdan ulaşabilirsiniz.

Not: Burada verilen kodlar eğitim amaçlıdır hiçbir şekilde ticari bir garanti verilmemektedir. Kullananın kendi sorumluluğundadır.
#The End

C++11 – Threadlere Cpu Tahsisi (Cpu Affinity)

C++ dilinin tercih edilmesinin en önemli sebeplerinden biri de performanstır. Hem düşük seviyeli hem de birden çok programlama yaklaşımına (procedural, OOP) destek vermesi dili farklı bir noktaya taşır.

Performans kayıplarının birçok sebebi olsa da en önemli sebeplerinden biri de ‘context switching’ dir. Yani işletim sisteminin takvimlendirmesine (scheduling) göre çalıştırılabilir dosyanızın farklı cpular veya corelar arasında durumunun değişerek taşınması. Bu durum aynı zamanda cpunun en hızlı şekilde eriştiği cache bellek alanlarının da sürekli farklı veri ile dolmasına sebebiyet verir. Bunu engellemek için herhangi bir core’ a posix thread kütüphanesini kullanarak programınızı atayabilirsiniz, örneğin

   cpu_set_t cpuset;
   CPU_ZERO(&cpuset);
   CPU_SET(core_id, &cpuset); // core_id atamak istediginiz core' un idsi
   pthread_t current_thread = pthread_self();    
   pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset);

şeklinde dileğimizi gerçekleştirebiliriz. Fakat yıl olmuş 2018, kimsenin std::thread varken posix threadi kullanmaz diye düşünüyorum :).

std::thread::native_handle

Daha önce bahsettiğimiz gibi std::thread sınıfı posix thread kütüphanesi üzerine bina edilmiş. native_handle fonksiyonu ise direkt olarak pthread_t türünden mevcut threadi döner. Bu bize her ne kadar standart kütüphanede thread affinityi gerçekleştirme imkanımız olmasa da pthread kütüphanesinin fonksiyonlarını kullanmamızı sağlar. O halde,

#include 
#include 
#include 

using namespace std;

#define  core_id  0

void func()
{
    int loop = 10000000;
    while (loop--) {
        cout << "loop:" << loop << endl;
    }

}

int main()
{
    thread th(func);

    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_id, &cpuset); // core_id atamak istediginiz core' un idsi

    pthread_setaffinity_np(th.native_handle(), sizeof(cpu_set_t), &cpuset);

    th.join();
    return 0;
}

şeklinde std::thread şablon sınıfını kullanarak niyetimizi gerçekleyebiliriz.

Unutmadan şu noktalara değinmekte fayda var, eğer cpunuz hyper threading teknolojisine sahipse kapatmak da fayda var. Hyper threading teknolojisi donanımsal coreları çoklayarak birden fazla core gibi kullanmayı sağlar biz de tam olarak bundan kaçıyoruz. Bir diğer optimizasyon ise işletim sistemine de sen sadece şu coreları kullanabilirsin diye ayar yapmak lazım. Aksi halde işletim sistemi sizin kendi programınız için ayırdığınızı core’u da kendi yönettiği herhangi bir process için kullanabilir. Bu da yine context switchinge sebep olabilir.

Bu şekilde kullanmak benim pek hoşuma gitmediği için, kendi kullanımım için std::threadi sarmalayan, daha önce öğrendiklerimizi de kullanarak bir sınıf yapısı kullanmak istedim. Bu sınıf yapısı tıpkı std::thread gibi değişken sayıda parametre alabilecek. İsteğe bağlı olarak std::threaddeki diğer yardımcı fonksiyonlar da eklebilir.


class DedicatedThread
{
public:
    template
    DedicatedThread(size_t core, Function&& f,  Args&&... args):
            mCore(core),
            mThread(std::forward(f), std::forward(args)...)
    {
       	      cpu_set_t cpu_set;
              CPU_ZERO(&cpu_set);
              CPU_SET(mCore, &cpu_set);
              pthread_setaffinity_np(mThread.native_handle(), sizeof(cpu_set_t), &cpu_set);
    }

    DedicatedThread(DedicatedThread &&r) : mThread(std::move(r.mThread)),
            						                                    mCore(std::move(r.mCore))
    {
    }

    DedicatedThread() = default;
    DedicatedThread(DedicatedThread &) = delete;
    DedicatedThread &operator=(const DedicatedThread &) = delete;
    DedicatedThread & operator=(DedicatedThread && r)
    {
      	if (mThread.joinable())
      		  mThread.join();

      	mThread = std::move(r.mThread);
      	return *this;
    }

    ~DedicatedThread()
    {
      	if (mThread.joinable())
      		  mThread.join();
    }

    void join()
    {
      	if (mThread.joinable())
      	    mThread.join();
    }

private:
    size_t  mCore;
    std::thread    mThread;
};

class A
{

public:

void f()
 {
   size_t loop = 100000000000;
   while (loop--) {
   cout << "loop:" << loop << endl;
   }
 }

};

int main()
{
 A a;

DedicatedThread dt = std::move(DedicatedThread(2, &A::f, &a));

 dt.join();

return 0;
}

C++14 – Göze çarpanlar

C++14 – Lambda fonksiyonlarında yakalanan ifadelerde ilk değer

C++11 ile gelen özelliklerden biri olan lambda ifadelerinde içinde olduğu bilinirlik alanından value-copy veya reference ile değişkenler, lambda fonksiyonunun gövdesine enjekte edilebiliyordu.

C++14 teki değişiklik ile bu değişkenlere başlangıç değeri de verilebilmekte bu da aslında std::move edilebilmelerinin önünü açıyor.

#include <iostream>
#include <iomanip>


using namespace std;

int main()
{
    int i = 10;
    auto lambda1 = [i = 5] () {
         cout << i << endl;
    };

    lambda1();

    std::unique_ptr ptr(new int(10));
    auto lambda2 = [value = std::move(ptr)] {return *value;};

    cout << lambda2() << endl;

    return 0;
}

C++14 – Variable Template – Değişken şablonları

C++11 de gelen variadic templatelerle derleme zamanında iyi işler çıkarabiliyorduk. C++14’te derleme zamanına değişken şablonlar da (variable template) eklendi. Baya da hoş oldu 🙂

#include <iostream>
#include <iomanip>


using namespace std;

template
constexpr T pi = T( 3.14159265358979323);

template
constexpr const char* pi = "pi";

int main()
{

    cout << setprecision(20);
    cout << pi << endl;
    cout << pi << endl;

    return 0;
}

C++14 – [[deprecated]]
Deprecated olan neredeyse herşeyin bildirimi için C++14 standartlarında dile eklenen özelliktir.


#include <iostream>

using namespace std;

[[deprecated]] int f();

class [[deprecated]] my_class {
public:
    [[deprecated]] int member;
};

[[deprecated]] int i;

enum [[deprecated]] colors{
    RED, BLUE, BLACK
};


[[deprecated]]
typedef int type;

template  class templ;

template 
class [[deprecated]] templ {};

[[deprecated("g() is thread-unsafe. Use h() instead")]]
void g( int& x );

void h( int& x );

int main()
{

    int a = f(); // warning: 'f' is deprecated
    g(a); // warning: 'g' is deprecated: g() is thread-unsafe. Use h() instead
    my_class my;

    return 0;
}

Bu şekilde bildirimi yapılmış fonksiyonların, sınıfların, değişkenleri v.b kullanımı derleyici tarafından uyarı mesajları alınmasına sebep olur.

C++14 – Generic Lambdalar
C++11 ile gelen anonymous fonksiyonlar oluşturmamızı sağlayan lambdalar, C++14 ile birlikte auto anahtar sözcüğünün kullanımı sayesinde daha genel olarak tanımlanabilirler. Örneğin;

#include <iostream>
#include <vector>

using namespace std;

int main()
{

    vector ivec = {1, 2, 4, 4, 5, 6, 7, 8, 2, 8};

    //C++11
    auto printVec11 = [] (std::vector &vec) {
        for (auto i : vec)
            cout << i << " ";
    };

    //C++14
    auto printVec14 = [] (auto &vec) {
        for (auto i : vec)
            cout << i << " ";
    };

    printVec11(ivec);
    cout << "\n";
    printVec14(ivec);

    return 0;
}

C++11 – std::async, std::packaged_task std::future ve std::promise

Daha önceki std::thread yazımızda yeni gelen standartlarla beraber C++ dilinin artık doğal olarak multi-threadingi desteklediğinden bahsetmiştik. Bu yazıda ise C++11 ile gelen multi-threading desteğinin sadece std::thread’ten ibaret olmadığından bahsedeceğim.

std::async

std::thread’ e benzer şekilde çalışır. Argüman olarak geçilen çağırılabilir (callable) nesneleri veya fonksiyonları asenkron olarak işletir ve bize std::future türünden bir nesne döner. std::future şablon sınıfı bize başlattığımız fonksiyonun veya nesnenin akıbeti hakkında bilgi sahibi olmamızı sağlar.

std::async şablon sınıfının ilk argümanı oluşturulan ‘görevin’ işleyişi (launch policy) hakkında bilgi verilmesi içindir. Şöyle ki;

  • std::launch::async
    Bu parametre oluşturulan görevin asenkron olarak (async execution) oluşturulmasını garanti eder. Yani verilen fonksiyon kesinlikle başka ve yeni bir threadde işleyecektir.
  • std::launch::deferred
    Deferred ile oluşturulan görev asenkron olarak başlamayıp ana threadde future objesinin get() fonksiyonu çağırıldığı zamanda (lazy evaluation) işlemesini sağlar. Kısacası ana thread sonuç isteyene kadar herhangi bir değerlendirme yapılmaz.
  • std::launch::async | std::launch::deferred
    Varsayılan davranıştır. Operasyonun yeni threadde mi yoksa ana threadde daha sonra mı değerlendirileceği implementasyon bağımlıdır. std::async’ in implementasyonu buna karar verir.

Örneğin;


#include <iostream>
#include <thread>
#include <future>

using namespace std;

int fact (int i)
{
    int ret = 1;
    while (i > 0) {
        ret = ret * i;
        i--;
    }

    cout << "async fact" << endl;
    return ret;
}

int doSomething()
{

    std::this_thread::sleep_for(std::chrono::seconds(3));

    cout << "main thread do something" << endl;

    return 0;
}

int main()
{
    std::future<int> ret = std::async(std::launch::async, fact, 5);

    doSomething();

    cout << ret.get() << endl;

    return 0;
}

Kodu çalıştırdığınızda çok büyük bir olasılıkla aşağıdaki gibi bir çıktıya sahip olursunuz.

async fact
main thread do something
120

Görüldüğü gibi fact() fonksiyonu işini asenkron olarak çalışarak daha önce bitirmiş, ana threadde ihtiyacı olduğu anda fact() fonksiyonun geri dönüş değerini future şablon sınıfı sayesinde elde etmiş.

std::promise < T >

Peki bu örneğin sonucunu geri dönüş değerinden değil de bir parametre olarak elde etseydik. Tıpkı modern c++ öncesi zamanlarda olduğu gibi pointer, mutex ve condition_variable üçlüsünü kullanmamız gerekirdi. İşte bu noktada std::promise bize bu imkanı sağlıyor.

#include <iostream>
#include <thread>
#include <future>

using namespace std;

void fact (int i, std::promise<int> *p)
{
    int ret = 1;
    while (i > 0) {
        ret = ret * i;
        i--;
    }

    p->set_value(ret);
}

int main()
{
    std::promise<int>  pobj;

    std::future<int> fut = pobj.get_future();

    std::thread th(fact, 5, &pobj);

    cout << "fact " << fut.get()  << endl;

    th.join();
    return 0;
}

std::promise < T > den okuma yapmak istediğimiz zaman kendi içerisinde barındırdığı future nesnesini elde etmemiz gerekiyor ve yine future < T > nesnesi ile değerimize ulaşabiliriz.

Hemen akla  “Neden iki nesne kullanılıyor tek bir nesne kullanılsa hem std::promise hem de std::future aynı işi görmüyor mu ?” diye bir soru gelebilir. Standartlar gereği threadler arasındaki durum veya veri paylaşımı half-duplex bir yapıda. Yani bir threade hem okuma hem yazma hakkı verilmiyor, nesnelerin yaşama sürelerini garanti altına almak için. Bu açıdan bakıldığında std::future consumer/reader iken std::promise producer/writer durumlarını yerine getirmekle mükellef.

std::packaged_task < T >

Aslında packaged_task std::thread ile std::async özelliklerinin daha özel bir hali. Normal bir fonksiyona std::future özelliğini ekleyerek std::thread ile asenkron çalıştırmanız denilebilir std::package < T > için.

#include <iostream>
#include <thread>
#include <future>

using namespace std;

int fact (int i)
{
    int ret = 1;
    while (i > 0) {
        ret = ret * i;
        i--;
    }

    cout << "ready for return" << endl;
    return ret;
}

int doSomething()
{

    std::this_thread::sleep_for(std::chrono::seconds(3));

    cout << "main thread do something" << endl;

}

int main()
{
    std::packaged_task<int (int)> pt(fact);

    auto fut = pt.get_future();

    std::thread th(std::move(pt), 5);

    doSomething();

    cout << fut.get() << endl;

    th.join();

    return 0;
}

Unutmadan şunu da eklemekte fayda var, std::package_task < T > sadece movable’dır herhangi bir şekilde kopyalanamaz o nedenden örnek kodda threade argüman olarak verilirken std::move şablon sınıfını kullandık.

C++11 – std::mem_fn, std::bind, std::function

C veya C++ programlama dilinde parametre olarak bir fonksiyon vermek istediğimizde veya bu fonksiyonu bir değişkene atamak istediğimizde ihtiyacımız olan özellik fonksiyon göstericileriydi (function pointers). Ama göstericiler zaten insanları korkuturken fonk. göstericilerinin o heybetli tanımı iyice korkutucuydu. 🙂

Modern C++ fonksiyon göstericileri getirdiği sınıflar ile daha modern ve kullanışlı bir hale getiriyor.

Aşağıda C stili bir fonksiyon gösterici tanımı ve çağrılması var.

#include <iostream>


int add(int a, int b)
{
    
    return a + b;
}


using namespace std;


int main(int argc, char* argv[])
{
    int (*f)(int, int) = add;
     //auto f = add; 
    
    cout << f(5, 6) << endl;
}

Aslında C++11 ile gelen özelliklerden biri olan auto anahtar sözcüğü ile bu zahmetli tanımdan kurtulabiliriz (yorum satırında örnek kullanımı görebilirsiniz). Ya da bir fonksiyona parametre olarak verilip callback olarak kullanılacaksa bu “türü” typedef anahtar sözcüğü ile daha anlaşılır hale getirebiliriz;

#include <iostream>


typedef int(*callback_add_t)(int, int);

int add(int a, int b)
{
    
    return a + b;
}


int foo(int a, int b, callback_add_t func)
{
   return  func(a, b);
}

using namespace std;


int main(int argc, char* argv[])
{
    cout << foo(5, 6, add) << endl;
}

Bu yazıda bu yaklaşımdan ziyade C++11 ile gelen fonksiyonlar üzerinde programcıyla mutlak hakimiyet sağlayan std::mem_fn, std::bind, std::function şablon sınıfları ve fonksiyonlarından bahsetmek istiyorum.

std::mem_fn:
Üye fonksiyonları sarmalayan bir fonksiyon şablon sınıfıdır. Sınıf üye fonksiyonlarını çağırmak için kullanılır.

#include <iostream>
#include <functional>


using namespace std;


struct SDummy
{
    void who_am_i()
    {
        cout << "Jackie Chan" << endl;
    }
    
    void how_old_am_i(int i)
    {
        cout << "I am " << i << "years old" << endl;
    }
    
};

int main(int argc, char** argv) 
{
    struct SDummy dummy;
    auto jchan = std::mem_fn(&SDummy::who_am_i);
    
    jchan(dummy);


    return 0;
}

mem_fn sınıfını kullanmak yerine member function pointer kullansaydık şöyle bir tanım yapmamız gerekecekti;

void (SDummy::*pMemberFunc)();

Bu fonksiyon göstericisini bir fonksiyona parametre olarak geçip kullanmak istiyorsaniz ait oldugu nesneyi de o fonksiyona parametre olarak vermeniz gerekmektedir. Ama daha kolay yollari var 🙂 .

std::bind

Adı üstünde aslında bir fonksiyonun istenildiği sekilde çağrılma kalıbına uygun bir obje oluşturarak saklamanızı sağlayan şablon sınıfıdır. Dilerseniz bu objeyi std::function sablon sınıfını da kullanarak rahatlıkla istenilen yere taşıyabilirsiniz.

#include <iostream>


using namespace std;


struct SDummy
{
    
    int operator()(int a, int b, int c, int d)
    {
        return std::max(std::initializer_list<int> {a, b, c, d}); 
    }
    
    
};

bool isGreaterThen(int a, int b)
{
    return a < b;
}

int main(int argc, char** argv) 
{
    SDummy s;
    auto f = std::bind(s, placeholders::_1, 0, placeholders::_2, placeholders::_3);
    
    cout << f(1, 2, 3) << endl;
    
    
    auto isGreaterThenFive = std::bind(isGreaterThen, 5, placeholders::_1);
    
    cout.setf(ios_base::boolalpha);
    cout << isGreaterThenFive(3) << endl;

    return 0;
}

Örneğimizde gördüğünüz gibi bir çağırılabilen bir nesneyi çeşitli parametrelerle ‘bind’ ediyoruz. ‘placeholders::n’ lar aslında bizde ‘bind’ edilen nesneye daha sonrasında, çağrılma esnasında çeşitli parametrelere çağırma esnekliği sunuyorlar. Dikkat ederseniz std::bind şablon sınıfının geri dönüş değerini kısmen gizledik aslında o bir std::function şablon sınıfı türünden bir nesne.

std::function

Evet aslında function şablon sınıfının kullanimi size fonksiyon göstericilerini hatırlatacak. Kısaca function şablon sınııi imzasını verdiğiniz fonksiyonları obje olarak muhafaza eder.

#include <iostream>


using namespace std;


struct SDummy
{
    
    void print(int a, int b, int c)
    {
        cout << "I am Dummy Struct " << a << ", "<< b << ", " << c << endl;
    }
    
    int operator()(int a, int b, int c, int d)
    {
        return std::max(std::initializer_list<int> {a, b, c, d}); 
    }
    
    
};

bool isGreaterThen(int a, int b)
{
    return a > b;
}

int main(int argc, char** argv) 
{
    std::function<bool(int, int)> f = isGreaterThen; //1
    
    cout << f (3, 5) << endl;
    
    std::function<void()> f_lambda = []() { cout << "I am lambda" << endl; };//2
    
    f_lambda();

    SDummy s;
    std::function<int(int, int, int)> maximumOne = std::bind(s, placeholders::_1, 0, placeholders::_2, placeholders::_3); //3
    
    cout << maximumOne(1, 2, 3) << endl;
        
    std::function<void(int, int)> dummyPrint = std::bind(&SDummy::print, s, placeholders::_2, 0, placeholders::_1); //4 
    
    dummyPrint(4, 5);
    
    return 0;
}

Görüldüğü üzere std::function içerisinde fonksiyonları rahatlıkla muhafaza edebiliyoruz. Örneklerde yer vermedim ama bu nesnelerin rahatlıkla parametre olarak bir başka fonksiyona veya sınıfa verllebileceği de aşikar. Onunla ilgili örnekleri de size bırakıyorum.

1′ nolu örnekte görüldüğü üzere bool(int, int) imzalı fonksiyonu direkt fonksiyon objesi içinde muhafaza edebiliyoruz. ‘2’ nolu örnekte ise yine modern cpp özelliklerinden bir lambda fonksiyonunun uygulamasını görüyoruz. ‘3’ nolu örnekte ise ‘bind’ ile özelleştirilen bir fonksiyonunun std::function içerisinde tutulmasını ve daha sonra çağrılmasını görebilirsiniz. Bu kullanım en yaygın kullanım şeklidir. ‘4’ nolu kullanımda ise bir sınıf üye fonksiyonunun std::function şablon sınıfı ile nasıl muhafaza edilebileceğini görüyoruz. Burda dikkat edilirse ikinci parametre olarak üyesi olunan sınıf türünden bir nesne parametre olarak geçiliyor. Eh geçilmesi gerekiyor ki fonksiyon çağırılabilsin tabi üye fonksiyon static değil ise.

std::end(story)

Neden C++ öğrenmeliyim ?

Bu minvalde bir yazı yazmayı uzun zamandır düşünüyordum ki, Krzysztof Szatan’ ın bloğunda bu konuyla alakalı çok iyi bir şekilde ele alınan bir blog yazısı gördüm. Kendisinden izin alarak yazısını türkçeye çeviriyorum:

“C++ artık ölü mü ?”

Bunu 10 yıldır üniversiteye gittiğim günden beri duyuyorum. Java şu anda zaten enterprise yazılımlarda tercih edilen programlama dili, en azından ABD için bunu söyleyebilirim. Eğer programcıysanız az çok Joel Spolsky’ nin JavaSchools undan haberiniz vardır. Bu 2005 aralığındaydı. 2000 de ortaya çıkan C# ise Microsoftun Javaya bir cevabıydı aynı zamanda C++ ı bitirecek olan programlama dili olarak lanse edildi. Daha sonra dinamik programlama dönemi başladı. Ruby, Python popüler olmaya başladı. Bu dillerler beraber akla gelen sorular bu kadar kolay programlama imkanı varken neden herşey için C++’ ı kullanasın ki ? C++ sadece daha hoş bir C değil miydi ? Kaldı ki C de daha hoş bir assembly diliydi. Eğer bir saatiniz varsa Herb Sutter’ ın muhteşem konuşmasına ayırabilirsiniz. TIOBE endeksine baktığımızda son 15 yılda C++ ın 3. sırayı işgal ettiğini görüyoruz, ne dersiniz eski bir dil için hiçte fena değil bu derece bize farklı kapılar aralıyor…

Göstericileri (pointer) kim sever ki ?

Çoğu insan C++ dilini göstericiler ile özdeşleştirerek hata yapıyorlar. Benimde bu konuda kötü tecrübelerim var, çok boyutlu dizilerin adreslerinin fonksiyonlara parametre olarak verilmesi, gösterici aritmetiği, iki yönlü bağlı liste (doubly-linked list) bunların hepsi “C with classes” döneminden kalma. Belki de C++ ı bu şekilde öğretmek yanlış değildir. O halde tekrar düşün.

Tabiki profesyonel bir programcı göstericilerin (pointer) nasıl işlediğini bilmeli. Fakat bu modern C++’ a ait bir yaklaşım değil. C++ sürekli gelişiyor dün iyi bilinen bir çok şey artık değiştirilmesi gereken kötü kodlar haline geliyor.

cpptimeline

Aslında bir bakıma dil revizyon sürecinden geçiyor. Bjarne Stroustrup’ a göre: “C++11 yeni bir dil hissi veriyor, parçalar yerine oturuyor, yüksek seviyeli programlama dille eskisinden daha iyi kaynaşıyor ve daha etkili hale geliyor”. C++ yeni gelen standartlarla beraber lambdalar, memory mode, aralık tabanlı döngüler, sağ değer referansları, değişken parametreli şablonlar v.s ile birçok görevi daha yapılabilir hale getiriyor. Diyelim ki eskiden kalma kodların var ve bunların yeni standartlara göre elden geçmesi lazım. Eski kodların yeni standartlara nasıl dönüştürüleceğini söyleyen statik kod analizi yapan ve öneriler sunan araçlar hizmetinde. Bu tarz araçların C++ da yokluğu her zaman sorun olmuştur, sadece Java nın mı otomatik refactor işlemi sunduğunu sanıyorsun ? Google’dan LLVM/clang gurusu Chandler Carruth a kulak verelim, bize 100 milyon satır C++ kodunu nasıl tek seferde refactor ettiğini anlatsın.

Daha Önce Yazılmış Kodlar Gerçeği

Birçok bilgisayar mühendisliği öğrencisi kariyerlerinin başlangıcından itibaren süper kahraman gibi kod yazacaklarını düşler. Fakat genelde 20 yıllık kodlarla karşılaşır. Sabit buffer ile kullanılmış onlarca kaynak dosyaya dağılmış yüzlerce ‘strcpy’, binlerce satır uzunluktaki fonksiyonlar, 5 farklı versiyonu bulunan bağlı liste implementasyonları işte temizlemen gereken birkaç problem. “İştahını sonraya sakla junior, dün çıkması gereken release daha çıkmadı GCC nin yeni versiyonuna geçemeyiz, diğer takım buna hazır değil” diyen yöneticiler. Tam bu noktada “nerede hata yaptım” sorusu akla gelebilir.

Bu problem C++ diline özel değil. Javada, PHP de hatta Ruby ve Python da bu tarz işlerle karşılaşacaksın. Aslında bu bir çok şirketin acı gerçeği. İşte tam bu noktada söyleyeceğim şey, her programlama işinde business tarafta az vakit harcayarak eğleneceğiniz ve zorluklarla savaşacağınız işler vardır. İşte tam buralarda C++ parlıyor.

C++’ın Kral olduğu alanlar: Oyun, Finans, HPC, Derleyiciler

Eğer hayatının tamamında CRUD implemente etmek istemeyen biriysen mutlaka C++ öğrenmelisin. İşte önerilerim;

Oyun endüstrisi :Neredeyse tüm AAA oyunları C++ ile geliştirilmiş. Yazılım dünyasında da oyun geliştiricilik saygı duyulan ve en çok talep edilen alanlardan biri. Performans çok önemli dolayısıyla etkin kod yazmak çok önemli. C++ da derinleşmekten çok oyun endüstrisinde işin teoriğini öğrenmek, semantiğini kavramak ve birtakım API veya kütüphaneleri öğrenmek daha önemli. Matematik alt yapısı ve grafik bilgisi bu alan için gerekli. Bu endüstri içinde tüm kariyeriniz boyunca öğrenecek bol bol materyal mevcut.

Yüksek performanslı uygulamalar – HPC: Bu tarz uygulamalar yüksek hızlarda olabildiğince fazla işlem yapmayı gerektirir. Donanım bilgisine sahip olmanız gerekir. Ve bu bilgiyi direkt dile uygulayabilmeniz gerekmektedir. Paralel programlama kütüphanesi , CUDA ve OpenCL bu denli bir uzmanlığın C++ ile ortaya konan ürünleri. Eğer bilimsel hesaplamaya meraklı isen bu tarz bir çok iş var bu aralar, hiç makina öğrenmesini duydun mu ? Bu ara baya popüler.

Derleyiciler: Bu alan benim favorim. LLVM projesi büyük bir başarıya sahip şöyle ki LLVM nin tezgahından geçmemiş veya kütüphanelerini kullanmamış popüler bir programlama dili bulamazsınız. Proje tamamen C++. Derleyiciler bir çok programlama diliyle yazılsa da teori implementasyonun gerisinde kalıyor tıpkı diğer durumlardaki gibi. İlgilenmen gereken alanlar optimizasyon, statik analiz, debugging, standart kütüphaneler, linker ve birtakım teorik mühendislik problemleri.

Finans: Eğer finans alanında bilgi sahibi iseniz, gerçek zamanlı gecikme kabul etmeyen birçok işin olduğundan haberdarsınızdır. Matematik bilgisi bu alan için çok büyük bir artı. Bu işlerin birçoğu yüksek frekanslı alım-satım platformlarını geliştirmek ve bakımlarını yapmak diyebiliriz. Aslında bu çok güzel bir alan eğer kendinizi programcı olarak tanımlamıyorsanız.5. Bu tarz işlerin ciddi maddi getiri ve büyük faydalar sağladığını da belirtmek isterim.

Bunlar çalışma alanı önerilerinden birkaçı. Bu alanların ortak özelliği birkaç tane java çaylağını bu alanlarda çalıştırıp işi yapmasını bekleyemezsiniz. Alan bilgisinin yoğun olduğu ve tecrübenin zorunlu olduğu alanlar bunlar. Yani bu da kolay kolay vazgeçilebilecek insanlar değiller. Şirketler bu tarz insanları elde tutmak için daha çok maaş ödeyecek ve çözmeleri için ilginç problemleri onlara havale edecek. Tabi ki her işte biraz maraz olur ama gerçekten değer verdiğin ve sevdiğin işlerle uğraşıyorsan bunları görmezden gelebiliyorsun.

C++11 – std::mutex

Multi-process programlamada her bir processin kendine ait bir bellek alanı (memory space) vardır. Her bir process kendisine lazım olan veriyi kendisi elde eder. Herhangi bir veri paylaşımı gerektiğinde ise inter-process communication araçlarından birini (pipe, shared memory v.s) tercih ederek gerekli veri processler arasında dağıtılmış olur.

Multi-thread programlamada ise tüm threadlar aynı bellek alanını paylaşırlar. Bu paylaşım verinin değiştirilmesi veya okunması sırasından bir senkronizasyonu zorunlu kılar. Bunun temel amacı yarış durumundan (race condition) korunmaktır. Bu noktada programcıların en sık başvurduğu metod ise mutual exclusion yani mutexlerdir.

Şimdi aşağıdaki programa bir göz atalım.

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <algorithm>


std::vector<int> ivec;
 
void addToVector(int i)
{
    ivec.push_back(i);
}

int main()
{
    std::vector<std::thread> tvec;

    for (int i = 0; i < 10; i++) {
        tvec.push_back(std::thread(addToVector, i));
    }
    
    std::for_each(tvec.begin(), tvec.end(), [](std::thread &th) {th.join();});

    
    for (auto i : ivec) 
        std::cout << i << std::endl;
    
    return 0;
}

Kod parçası 10 thread oluşturuyor ve bu threadi sırasıyla kendisine geçilen parametre değerleriyle beraber çağırıyor. Bu programın çıktı olarak ne vermesini beklersiniz ?

Herhangi bir çıktıya sahip olabilir misiniz, hatta programınız seg fault alarak da sonlanabilir veya hiçbir şey olmadan ilk bakista beklenildiği gibi,

0
1
2
3
4
5
6
7
8
9

şeklinde bir çıktı da alabilirsiniz. Kısacası bu durum bilinmez veya kestirilemez (undefined behaviour). Peki burdaki yarış durumu nerde oluşmaktadır, kısacası kodun hangi bölümünde threadler için kritik ve mutex ile koruma altına alınması gerekir ?

Cevap aslında threadler nerede ortak alana müdahale ediyorlarsa orada mutex kullanmamız lazımç Hepimizin az çok tahmin ettiği gibi threadlerin oluşturulup ivec vectorune threadler veri eklerken yarış durumu oluşur. Eğer bu bölgede mutexler vasıtasıyla koruma sağlanırsa bilinmezlik durumu ortadan kalkar ve threadlerin yarış durumuna girmeden herbirinin güvenli bir şekilde çağrılması garanti altına alınmış olur. Aşağıdaki örnek kodu inceleyelim;

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <algorithm>

std::vector<int> ivec;
std::mutex mtx;
 
void addToVector(int i)
{
    mtx.lock();
    ivec.push_back(i);
    mtx.unlock();
}

int main()
{
    std::vector<std::thread> tvec;

    for (int i = 0; i < 10; i++) {
        tvec.push_back(std::thread(addToVector, i));
    }
    
    std::for_each(tvec.begin(), tvec.end(), [](std::thread &th) {th.join();});

    
    for (auto i : ivec) 
        std::cout << i << std::endl;
    
    return 0;
}

lock() ve unlock() üye fonksiyonları, aralarında kalan bloğun thread-safe olmasını garanti altına almaktadır. Yeri gelmişken bunu da belirtmeden geçmeyelim, kilitli (locked) durumundaki bir mutexi tekrar kilitlemeye (lock) çalışmak yine belirsizlik durumuna (undefined behaviour) sebep olur.
Dalgınlıktan dolayı unlock() fonksiyonunu çağırmayı unutursa programcı işte o zaman program bloklanır. Bu tarz hatalardan korunmak için yine akıllı göstericilerde kullanıldığı gibi RAII semantiği yardımımıza koşuyor.

lock_guard :

lock_guard ctor’ına geçilen mutex nesnesi, o aşamada kilitleniyor. Daha sonra lock_guard türündeki değişken dtor edildiginde sahip olunan mutex nesnesi ‘unlock’ ediliyor. Yani geliştirici unutsa bile iş otomatik olarak RAII makenizmasına emanet edildiği için başını yastığa rahat koyabilecek. 🙂

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <algorithm>

std::vector<int> ivec;
std::mutex mtx;
 
void addToVector(int i)
{
    std::lock_guard<std::mutex> grd(mtx);
    ivec.push_back(i);
}

int main()
{
    std::vector<std::thread> tvec;

    for (int i = 0; i < 10; i++) {
        tvec.push_back(std::thread(addToVector, i));
    }
    
    std::for_each(tvec.begin(), tvec.end(), [](std::thread &th) {th.join();});

    
    for (auto i : ivec) 
        std::cout << i << std::endl;
    
    return 0;
}

Yukarıdaki kod örneğinde görüldüğü üzere yaptığım tek değişiklik std::mutex sınıfının lock() unlock() fonksiyonları yerine işi direkt lock_guard şablon sınıfına havale ettim.

unique_lock :

unique_lock şablon sınıfı aslında lock_guard sınıfının daha esnek ve seçenek sunan halidir. Örneğin,

std::unique_lock<std::mutex> ulock (mtx, std::defer_lock);

unique_lock sınıfının ctor ının ikinci parametresine std::defer_lock parametresi geçilerek inşaa aşamasında (construction) lock işleminin yapılmamasını sağlayabilirsin. Daha sonra bu işlemi sağlamak için unique_lock sınıfı lock() ve unlock() fonksiyonlarını hizmet olarak sunmaktadır. Ayrıca unique_lock sınıfı nesneleri kopyalanamazlar (non-copyable) ama taşınabilirlerdir (movable).

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <algorithm>

std::vector<int> ivec;
std::mutex mtx;
 
void addToVector(int i)
{
    std::unique_lock<std::mutex> grd(mtx, std::defer_lock);
    grd.lock();
    ivec.push_back(i);
    grd.unlock(); //1
}


int main()
{
    std::vector<std::thread> tvec;

    for (int i = 0; i < 10; i++) {
        tvec.push_back(std::thread(addToVector, i));
    }
    
    std::for_each(tvec.begin(), tvec.end(), [](std::thread &th) {th.join();});

    
    for (auto i : ivec) 
        std::cout << i << std::endl;
    
    return 0;
}

1 nolu satırda mutexi unlock etmeyi unutsanız bile bunu sizin yerinize unique_lock sınıfının dtor ı yapacaktır. Bu yüzden direkt mutex sınıfının lock ve unlock fonksiyonlarını kullanmak yerine ihtiyacınıza göre kesinlikle lock_guard ve unique_lock gibi yardımcı sınıfların kullanımlarını alışkanlık haline getirmekte fayda var.

Genel olarak yeni gelen standartlarda mutexler bu şekilde ele alınmış. Burda ekstra deadlock nedir, nasıl korunulur gibi konulara girmedim. Bu konular üzerine ayrıca uzun uzun yazılar yazılabilir. Ama multi-thread programlama yapma niyeti olan arkadaşların kesinlikle deadlock gibi durumlardan korunacak disiplinleri kazanması ve bu konuda okumalar yapması şiddetle tavsiye edilir.

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…

New Meetup: Reasoning on transactional programming

Verifying Programs under Snapshot Isolation and Similar Relaxed Consistency Model

In this talk, I will present a static verification approach for programs running under snapshot isolation (SI) and similar relaxed transactional semantics. Relaxed conflict detection schemes such as snapshot isolation (SI) are used widely. Under SI, transactions are no longer guaranteed to be serializable, and the simplicity of reasoning sequentially within a transaction is lost. In this paper, we present an approach for statically verifying properties of transactional programs operating under SI. Differently from earlier work, we handle transactional programs even when they are designed not to be serializable.

I will present a source-to-source transformation which augments the program with an encoding of the SI semantics. Verifying the resulting program with transformed user annotations and specifications is equivalent to verifying the original transactional program running under SI — a fact we prove formally. Encoding which I present preserves the modularity and scalability of VCC’s verification approach. I will show the application of the method successfully to benchmark programs from the transactional memory literature.

by İsmail Kuru

PS: Time and Place will be determined in related with participant count and suggestions.

Not: Şimdilik yer ve zaman konusu net değil. Katılımcı sayısı ve istekleri doğrultusunda kesin olarak karar verilecektir.

http://goo.gl/GFwDVY