Merhaba, QT’de Thread Desteği adlı seride daha önceki yazımda bahsettiğim thread teknolojileri ile ilgili birkaç örnek kodu bu yazıda inceleyeceğiz.

Bu teknolojilerin kullanım amacından ve projemize uygun teknolojiyi nasıl seçeceğimizden bahsetmiştik, tekrar hatırlamak gerekirse bu teknolojileri aşağıdaki şekilde sıralayabiliriz:

Multithreading Teknolojileri

  • QThread
  • QThreadPool and QRunnable
  • Qt Concurrent
  • WorkerScript

Önemli bir noktaya değinmek gerekirse yukarıdaki thread teknolojilerinden WorkerScript’i mecbur kalmadıkça kullanmamanızı öneririm çünkü QT ile arayüz tasarımında Qt Quick ile QML ve C++ kullanarak bir arayüz geliştiriyorsak yapımızı görünüm kısmında QML, lojik kısmında C++ olacak şekilde tasarlamalıyız. Sonuç olarak, mantıksal işlemleri C++’da gerçekleştirip, QML’i sadece arayüz tasarımı için kullanmak, hem performans hem de kodun bakımı açısından daha iyi bir yaklaşımdır. Bu, hem daha iyi performans hem de daha temiz ve düzenli bir kod tabanı sağlar.

Threadsiz Örnek

Thread örneklerimizde basit bir arayüz ve buton üzerinden ilerleyeceğiz. Arayüzdeki buton arkaplanda bir threadi çağıracak ve fonksiyonun yürütülmesini sağlayacaktır. Thread ile yapacağımız örneklere geçmeden önce thread olmadan arayüzün nasıl bir tepki vereceğini görelim.

Arayüz

Uygulamamızın kodları, Main.qml

import QtQuick 2.15
import QtQuick.Controls 2.15

Window {
    visible: true
    width: 300
    height: 200
    title: "Thread örnekleri"

    Button {
        id: startButton
        width: 100
        height: 20
        text: "Thread'i Başlat"
        anchors.centerIn: parent
        onClicked: {
            // Butona basıldığında threadi başlat
            worker.doWork();
        }
    }
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "worker.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    // Worker nesnesi olusturma
    Worker worker;

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("worker", &worker);

    engine.loadFromModule("ThreadLearning", "Main");

    return app.exec();
}

worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);

public slots:
    void doWork();
};

#endif // WORKER_H

worker.cpp

#include "worker.h"
#include <QDebug>
#include <QThread>

Worker::Worker(QObject *parent) : QObject(parent) {}

void Worker::doWork() {

    for(int i = 0; i < 5; ++i) {
        qDebug() << "Çalışıyor...";
        QThread::msleep(1000);
    }
    qDebug() << "İşlem tamamlandı!";
}

Not: worker.cpp içerisinde QThread::msleep(1000) ifadesi fonksiyonun birer saniye aralıklarla çalışması içindir, uygulamamızı gözlemleyebilmek için çalıştırdığımız fonksiyonun belli bir süre çalışmasına ihtiyacımız var.

worker sınıfımız buton tarafından çağrılacak fonksiyonu içeriyor, her bir thread örneğinde worker sınıfımızdaki doWork fonksiyonu üzerinde oynayarak farklı yapıları nasıl uygulayacağımızı göreceğiz. Main.cpp ve qml dosyamız ise arayüzün tasarımını ve temel bağlantıları ve yapıları içeriyor, bu dosyalarda herhangi bir değişiklik yapmayacağız.

Qt Creator’da kodları çalıştırıp arayüzdeki butona tıkladığınızda, beklenen çıktıyı alırsınız. Ancak, butona tıkladığınızda arayüzün donduğunu ve tepki vermediğini gözlemleyebilirsiniz. Bu durum, arka plandaki ana threadin, işlev tamamlanana kadar beklemesinden kaynaklanır. Ana thread, kullanıcı arayüzü ve uygulamanın ana döngüsünü işler. Bu nedenle, uzun süren işlemler ana thread üzerinde çalışıyorsa, arayüz etkileşimi yanıt vermez ve donar.

Kullanıcı dostu bir deneyim sağlamak için, uzun süren işlemleri ana threadden ayırarak arka planda bir threadi çalıştırmalısınız. Bu sayede, ana thread arayüzün donmamasını sağlar ve kullanıcı etkileşimine izin verir. Böylece, arayüz kullanıcı dostu ve akıcı bir şekilde çalışırken, arka plandaki işlemler de devam eder.

Qthread

QThread Qt’deki tüm thread yapılarının temelidir. Doğrudan iş parçacıklarının oluşturulması, başlatılması, durdurulması gibi thread yönetimi işlemlerini gerçekleştirmenizi sağlar. Bu durum, daha özelleştirilmiş thread davranışları gerektiğinde veya daha fazla kontrol sağlanması gerektiğinde avantajlı olabilir.

QThread sınıfı hakkında daha fazla bilgi için: https://doc.qt.io/qt-6/qthread.html

QThread’i kullanmanın birçok yolu var buradaki örneğimizde lambda fonksiyonu içerisinde işlemlerimizi tanımlıyoruz.

#include "worker.h"
#include <QDebug>
#include <QThread>

Worker::Worker(QObject *parent) : QObject(parent) {}

void Worker::doWork() {
    // Worker işlemlerini yeni bir thread içinde gerçekleştirir
    QThread *thread = QThread::create([=]() {
        
        for(int i = 0; i < 5; ++i) {
            qDebug() << "Çalışıyor...";
            QThread::msleep(1000);
        }
        qDebug() << "İşlem tamamlandı!";
    });

    // Thread'i başlatır
    thread->start();

    // Thread tamamlandığında belleği serbest bırakır
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
}

worker.cpp dosyamızdaki kodu güncelleyip uygulamamızı tekrar çalıştırıp butona tıkladıktan sonra arayüzün sürüklenebilir ve butona tekrar tıklanabilir olduğunu görebilirsiniz. Bu şekilde, doWork fonksiyonu bir thread içinde çalışacak ve ana iş parçacığı (main thread) üzerinde çalışmayacak. Uygulamanızın daha iyi performans göstermesine ve etkileşimli olmasına yardımcı olacaktır.

QThread’i lambda fonksiyonu ile kullanmanın yanı sıra Subclass ve moveToThread yöntemleriylede kullanabiliriz.

Lambda Fonksiyonu Yöntemi:

  • Lambda fonksiyonu yöntemi, C++11’den itibaren QThread::create fonksiyonu ile kullanılabilir hale gelmiştir. Bu yöntem, iş parçacıklarını daha basit ve doğrudan bir şekilde oluşturmanıza olanak tanır.
  • Genellikle daha küçük ve tek seferlik işler için kullanılır.
  • Kodun daha kompakt olmasını sağlar ve bir lambda fonksiyonu içinde iş parçacığı mantığını ifade etmenize olanak tanır.

moveToThread Yöntemi:

  • moveToThread yöntemi, var olan bir QObject örneğini, belirtilen bir QThread örneğine taşır.
  • Genellikle, QObject‘in bir iş parçacığında çalışmasını sağlamak için kullanılır. Özellikle, uzun süren işlemleri ana iş parçacığından ayırmak için kullanışlıdır.
  • Bir nesne bir defaya mahsus taşınabilir. Yani, başka bir thread’e taşındıktan sonra başka bir thread’e taşınamaz.

Subclass Yöntemi:

  • Subclass yöntemi, QThread sınıfından türetilen bir alt sınıf oluşturmayı içerir. run işlevini yeniden tanımlayarak alt sınıfın çalışma mantığını belirtiriz.
  • Thread’e özgü işlemleri alt sınıfın içine yerleştirebiliriz.
  • Thread oluşturma ve yönetme sorumluluğu kodunuzun bir parçası olarak kalır.

Her bir yöntemin kullanımı duruma ve ihtiyaca bağlıdır. Özellikle, thread yönetimi, işlemler ve senkronizasyon gereksinimleri dikkate alınarak seçilmelidir.

QThreadPool ve QRunnable

QThreadPool ve QRunnable, Qt’de çoklu thread programlamasını sağlayan araçlardır. QThreadPool, threadleri yönetmek için bir havuz sağlar. Bu havuz, threadleri oluşturur, başlatır, sonlandırır ve gerektiğinde tekrar kullanır.
QRunnable, thread tarafından yürütülecek kodu temsil eden bir arayüz sınıfıdır. Kod, run fonksiyonunda tanımlanır. Birlikte kullanıldığında, QThreadPool‘a bağlı QRunnable nesneleri, thread havuzuna gönderilir ve orada uygun bir thread tarafından asenkron olarak çalıştırılır. Bu sayede, kodunuzu tekrar kullanabilirsiniz, çünkü aynı QRunnable nesnesi farklı threadlerde birden çok kez kullanılabilir, böylece işlemleri daha verimli ve paralel hale getirebilirsiniz.

qt.io tanımı
Threadleri sık sık oluşturmak ve yok etmek pahalı olabilir. Bu yükü azaltmak için mevcut threadler yeni görevler için yeniden kullanılabilir. QThreadPool, yeniden kullanılabilir QThread’lerin bir koleksiyonudur.
Kodu bir QThreadPool’un threadlerinden birinde çalıştırmak için, QRunnable::run()’u yeniden uygulayın ve alt sınıflanmış QRunnable’ı başlatın.

Uygulama kodumuz, worker.h

burada QRunnable arabirimini miras alarak (public QRunnable) run() fonksiyonunu override ettik. run() fonksiyonu thread başlatılınca QRunnable tarafından çağrılacak fonksiyondur.

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QRunnable>

class Worker : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit Worker();
    ~Worker() override;

    // QRunnable interface
    void run() override;

public slots:
    void doWork();
};

#endif // WORKER_H

worker.cpp

#include "worker.h"
#include <QDebug>
#include <QThread>
#include <QThreadPool>

// Worker sınıfının kurucu fonksiyonu
Worker::Worker()
{
    qDebug() << "Worker constructor running in thread : " << QThread::currentThread();
}

// Worker sınıfının yıkıcı fonksiyonu
Worker::~Worker(){
    qDebug() << "Worker destructor running in thread : " << QThread::currentThread();

}

// QRunnable sınıfının run() fonksiyonunu yeniden implemente ediyoruz
void Worker::run()
{
    qDebug() << "Worker run method running in thread : " << QThread::currentThread();

    for(int i = 0; i < 5; ++i) {
        qDebug() << "Çalışıyor...";
        QThread::msleep(1000);
    }
    qDebug() << "İşlem tamamlandı!";
}

void Worker::doWork() {
    // Yeni bir Worker nesnesi oluşturulur
    Worker * worker = new Worker();

    // QRunnable'ı QThreadPool'un çalıştırma kuyruğuna koymak için
    // QThreadPool::start() kullanarak threadi çalıştırıyoruz
    QThreadPool::globalInstance()->start(worker);
}

QRunnable sınıfını miras alarak, run() fonksiyonunu override etmek ve çalıştırmak threadin temelini oluşturur. Bu şekilde, istediğimiz thread kodlarını run() fonksiyonunda tanımlayabiliriz. Ardından, bu thread QThreadPool veya benzeri bir yapıya eklenir ve thread havuzunda çalıştırılmak üzere hazır hale getirilir.

Ayrıca, sınıfın yapıcı ve yıkıcı fonksiyonlarını tanımlayarak, oluşturulan threadin ana threadden (main) farklı bir adres alanında başlayıp sona erdiğini görebiliriz.

Qt Concurrent

Qt Concurrent hakkında daha fazla bilgi için: https://doc.qt.io/qt-6/qtconcurrent-index.html

qt.io tanımı:
Qt Concurrent modülü, muteksler, okuma-yazma kilitleri, bekleme koşulları veya semaforlar gibi düşük seviyeli iş parçacığı ilkellerini kullanmadan çok iş parçacıklı programlar yazmayı mümkün kılan yüksek seviyeli API’ler sağlar. Qt Concurrent ile yazılan programlar, kullanılan iş parçacığı sayısını mevcut işlemci çekirdeği sayısına göre otomatik olarak ayarlar.

QtConcurrent, Qt’nin yüksek seviyeli bir API’sidir ve çoklu thread programlamasını daha kolay hale getirir. QtConcurrent kullanırken, geliştiricilerin thread oluşturma, threadde çalışacak işi tanımlama ve thread sonuçlarını birleştirme gibi detaylarla uğraşması gerekmez. Bunun yerine, QtConcurrent’in sunduğu işlevler aracılığıyla bu işlemleri kolayca gerçekleştirebilir.

QtConcurrent kullanmanın avantajlarından biri, threadlerin yönetimini ve planlamasını tamamen Qt’in kendisine bırakmasıdır. Geliştiriciler, threadleri başlatmak ve sonuçlarını almak için basit, tek satırlık işlevler kullanabilirler. Bu, kodun daha temiz ve okunabilir olmasını sağlar ve geliştiricilerin thread yönetimiyle ilgili ayrıntılarla uğraşmasını önler.

Sonuç olarak, QtConcurrent kullanmak, thread programlamasını daha basit, daha güvenli ve daha verimli hale getirir. Geliştiricilerin threadle ilgili detaylarla uğraşması gerekmez ve Qt’in sağladığı güçlü çoklu thread desteğinden faydalanabilirler.

Not: QtConcurrent kütüphanesini kullanabilmek için Build System Generator dosyalarına kütüphaneyi tanıtmamız gerekir.

CMake
find_package(Qt6 REQUIRED COMPONENTS Concurrent)
target_link_libraries(mytarget PRIVATE Qt6::Concurrent)

qmake
QT += concurrent

Uygulama kodu, worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QFuture>

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker();
    ~Worker() override;

    static void runThread();

public slots:
    void doWork();

private:
    QFuture<void> future;
};

#endif // WORKER_H

burada static olarak tanımladığımız runThread fonksiyonu QtConcurrent::run(runThread) olarak çağrılacak, future özelliği ise, QFuture türünden bir nesnedir ve QtConcurrent modülünün bir parçasıdır. Bu nesne, QtConcurrent kullanılarak başlatılan asenkron işlemin durumunu, sonucunu ve ilerlemesini yönetir. Yani, bir iş parçacığı veya iş parçacığı havuzunda çalışan bir işlemi temsil eder.

worker.cpp

#include "worker.h"
#include <QDebug>
#include <QtConcurrent>

// Worker sınıfının kurucu fonksiyonu
Worker::Worker()
{
    qDebug() << "Worker constructor running in thread : " << QThread::currentThread();
}

// Worker sınıfının yıkıcı fonksiyonu
Worker::~Worker(){
    qDebug() << "Worker destructor running in thread : " << QThread::currentThread();

}


void Worker::doWork()
{
    future = QtConcurrent::run(runThread);
}

void Worker::runThread() {


    qDebug() << "Worker run method running in thread : " << QThread::currentThread();

    for(int i = 0; i < 5; ++i) {
        qDebug() << "Çalışıyor...";
        QThread::msleep(1000);
    }
    qDebug() << "İşlem tamamlandı!";
}

future ile thread yönetimini sağlayabiliriz, işlemin başlatılmasından sonra bu işlemi izlemek ve sonucunu gerektiğinde kullanmak için kullanılabilir. Aşağıda örnek kullanımları görebilirsiniz:

void on_cancelButton_clicked()
{
    future.cancel();
}

void on_pauseButton_clicked()
{
    future.pause();
}

void on_resumeButton_clicked()
{
    future.resume();
}

Sonuç

QThread, QRunnable, QThreadPool ve Qt Concurrent ile ilgili kod örneklerimiz yazdık ve inceledik, bu teknolojilerden hangisini hangi amaçla kullanmamız gerektiğini daha önceki yazımda bahsetmiştim, buraya tıklayarak ilgili yazıya ulaşabilirsiniz.

Sonraki yazıda Thread Senkronizasyonu konusundan devam edeceğiz, iyi çalışmalar 🙂

Kaynaklar:

https://doc.qt.io/qt-6/threads-technologies.html
https://doc.qt.io/qt-6/qthread.html
https://doc.qt.io/qt-6/qrunnable.html
https://doc.qt.io/qt-6/qtconcurrent-index.html

Anahatar kelimeler: qt thread kullanımı, qt thread desteği, qthread, qt thread nasıl kullanılır, qt thread örnekleri, qml thread kullanımı, thread örnekleri, arayüz thread kullanımı

Yorum bırakın

Trend

WordPress.com’da Blog Oluşturun.