3. derste kaldığımız yerden devam edelim en son görünümleri ve görünümlerin içerisine bileşenleri eklemiştik. Bileşenleri ekledikten sonra Qt Designer’da sağ tarafta bulunan Özellikler (Properties) bölümünde bileşenlerin özelliklerini tek tek belirleyeceğiz. Metin tarayıcıyı seçtikten sonra sağ tarafta bu bileşenin özellikleri gösterilmektedir. Nesne adı (objectName) özelliğini kullanarak bileşenin kodda hangi değişkenle temsil edileceğini belirleyebiliriz. Biz burada metin tarayıcısı için bufferBrowser değişkenini kullanacağız.
İlerleme çubuğunun önüne yerleştirdiğimiz etiket için progressbarLabel değiken ismini kullanacağız. Burada tek tek değişken isimlerini saymaya gerek görmüyorum. Kodu incelediğinizde zaten bütün bileşenlerin hangi isimlerle çağrılacağını görmüş olacaksınız. Aşağıda program için oluşturduğumuz arayüzün son hali gözüküyor.
Şimdi yine Qt Designer kullanarak uygulamamıza Sinyal ve Slot fonksiyonlarını tanıtacağız. Hatırlayacağınız üzere uygulamamızda iki buton olacaktı. Birincisi “Yaz”, diğeri “Oku” butonları. “Yaz” butonuna basıldığında uygulamamız kendisi için ayrılan belleğe rasgele seçerek A, C, G, T harflerinden birisini yazar. “Oku” butonuna basıldığında ise belleğe yazılan harfler okunarak ekranda metin tarayıcı bileşeninde gösterilir. Aşağıda tıklama (clicked) sinyalinin “Yaz” butonuna eklenmesi ve bu sinyale cevap olarak ana pencere sınıfına – bu uyguglama için SignalSlotExampleClass – on_writeButton_clicked slotunun eklenmesi gösterilmiştir. İşlemler sırasıyla şu şekildedir:
- Sinyal/Slot düzenleme moduna geçilmesi
- İlgili butonun seçilir. Bu sırada Qt Designer bu bileşenden yayılan sinyalin hangi bileşen tarafından karşılanacağını belirlemeniz için yol gösterir. Siz de istediğiniz bileşeni seçersiniz.
- İlk seçtiğiniz sinyal yayıcı bileşenden hangi sinyalin yayınlanacağı seçilir.
- Sinyale cevap verecek slot belirlenir.
- Qt Designer’da hazır bulunan slotlardan farklı bir slot kullanacağımız için yeni slotu + sembolüne basarak ekliyoruz.
- Yeni eklenen slotu göstermektedir.
Qt Designer ile bu slotun eklenmesi aslında sadece uygulamaya yapılmış bir bildirimdir. Slotun ne yapacağını henüz tanımlamadık. Qt Designer ile bu sinyal/slotun belirlenmesi koda aşağıdaki satırın eklenmesine sebep olur:
QObject::connect(writeButton, SIGNAL(clicked()), SignalSlotExampleClass, SLOT(on_writeButton_clicked()));
connect fonksiyonu programda hangi bileşenin hangi sinyali yayacağını ve cevap olarak hangi fonksiyonun çağrılacağını belirler.
Ardından readButton için benzer şekilde sinyal/slot ekleyeceğiz. Bu işlem de çok benzer olduğu için ayrıca göstermiyorum. Bu arayüzü kaydedip VS19 ile derlediğimizde aşağıdaki kodu bizim için otomatik olarak oluşturur. Standart bir uygulamada menü çubuğu (menu bar) ve araç çubuğu (tool bar) olduğu için VS19 ile standart bir Qt uygulaması açtığınızda bu iki bileşen otomatik olarak gelmektedir. Bunlara şimdilik takılmayın.
/********************************************************************************
** Form generated from reading UI file 'SignalSlotExample.ui'
**
** Created by: Qt User Interface Compiler version 5.9.9
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_SIGNALSLOTEXAMPLE_H
#define UI_SIGNALSLOTEXAMPLE_H
#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QProgressBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QSpacerItem>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_SignalSlotExampleClass
{
public:
QWidget *centralWidget;
QGridLayout *gridLayout;
QVBoxLayout *verticalLayout;
QVBoxLayout *verticalLayout_2;
QTextBrowser *bufferBrowser;
QHBoxLayout *horizontalLayout_2;
QLabel *progressbarLabel;
QProgressBar *numOfCharBar;
QHBoxLayout *horizontalLayout;
QSpacerItem *horizontalSpacer_2;
QPushButton *readButton;
QSpacerItem *horizontalSpacer;
QPushButton *writeButton;
QSpacerItem *horizontalSpacer_3;
QMenuBar *menuBar;
QToolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *SignalSlotExampleClass)
{
if (SignalSlotExampleClass->objectName().isEmpty())
SignalSlotExampleClass->setObjectName(QStringLiteral("SignalSlotExampleClass"));
SignalSlotExampleClass->resize(600, 400);
centralWidget = new QWidget(SignalSlotExampleClass);
centralWidget->setObjectName(QStringLiteral("centralWidget"));
gridLayout = new QGridLayout(centralWidget);
gridLayout->setSpacing(6);
gridLayout->setContentsMargins(11, 11, 11, 11);
gridLayout->setObjectName(QStringLiteral("gridLayout"));
verticalLayout = new QVBoxLayout();
verticalLayout->setSpacing(6);
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
verticalLayout_2 = new QVBoxLayout();
verticalLayout_2->setSpacing(6);
verticalLayout_2->setObjectName(QStringLiteral("verticalLayout_2"));
bufferBrowser = new QTextBrowser(centralWidget);
bufferBrowser->setObjectName(QStringLiteral("bufferBrowser"));
bufferBrowser->setReadOnly(true);
verticalLayout_2->addWidget(bufferBrowser);
horizontalLayout_2 = new QHBoxLayout();
horizontalLayout_2->setSpacing(6);
horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2"));
progressbarLabel = new QLabel(centralWidget);
progressbarLabel->setObjectName(QStringLiteral("progressbarLabel"));
horizontalLayout_2->addWidget(progressbarLabel);
numOfCharBar = new QProgressBar(centralWidget);
numOfCharBar->setObjectName(QStringLiteral("numOfCharBar"));
numOfCharBar->setMaximum(256);
numOfCharBar->setValue(0);
horizontalLayout_2->addWidget(numOfCharBar);
verticalLayout_2->addLayout(horizontalLayout_2);
verticalLayout->addLayout(verticalLayout_2);
horizontalLayout = new QHBoxLayout();
horizontalLayout->setSpacing(6);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer_2);
readButton = new QPushButton(centralWidget);
readButton->setObjectName(QStringLiteral("readButton"));
QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(readButton->sizePolicy().hasHeightForWidth());
readButton->setSizePolicy(sizePolicy);
horizontalLayout->addWidget(readButton);
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer);
writeButton = new QPushButton(centralWidget);
writeButton->setObjectName(QStringLiteral("writeButton"));
sizePolicy.setHeightForWidth(writeButton->sizePolicy().hasHeightForWidth());
writeButton->setSizePolicy(sizePolicy);
horizontalLayout->addWidget(writeButton);
horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addItem(horizontalSpacer_3);
horizontalLayout->setStretch(0, 1);
horizontalLayout->setStretch(1, 1);
horizontalLayout->setStretch(2, 1);
horizontalLayout->setStretch(3, 1);
horizontalLayout->setStretch(4, 1);
verticalLayout->addLayout(horizontalLayout);
gridLayout->addLayout(verticalLayout, 0, 0, 2, 3);
SignalSlotExampleClass->setCentralWidget(centralWidget);
menuBar = new QMenuBar(SignalSlotExampleClass);
menuBar->setObjectName(QStringLiteral("menuBar"));
menuBar->setGeometry(QRect(0, 0, 600, 21));
SignalSlotExampleClass->setMenuBar(menuBar);
mainToolBar = new QToolBar(SignalSlotExampleClass);
mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
SignalSlotExampleClass->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(SignalSlotExampleClass);
statusBar->setObjectName(QStringLiteral("statusBar"));
SignalSlotExampleClass->setStatusBar(statusBar);
retranslateUi(SignalSlotExampleClass);
QObject::connect(writeButton, SIGNAL(clicked()), SignalSlotExampleClass, SLOT(on_writeButton_clicked()));
QObject::connect(readButton, SIGNAL(clicked()), SignalSlotExampleClass, SLOT(on_readButton_clicked()));
} // setupUi
void retranslateUi(QMainWindow *SignalSlotExampleClass)
{
SignalSlotExampleClass->setWindowTitle(QApplication::translate("SignalSlotExampleClass", "SignalSlotExample", Q_NULLPTR));
progressbarLabel->setText(QApplication::translate("SignalSlotExampleClass", "Tampon Doluluk Oran\304\261 : ", Q_NULLPTR));
readButton->setText(QApplication::translate("SignalSlotExampleClass", "Oku", Q_NULLPTR));
writeButton->setText(QApplication::translate("SignalSlotExampleClass", "Yaz", Q_NULLPTR));
} // retranslateUi
};
namespace Ui {
class SignalSlotExampleClass: public Ui_SignalSlotExampleClass {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_SIGNALSLOTEXAMPLE_H
Uygulamamızın main fonksiyonu daha önce yazmış olduğumuz uygulamanın main fonksiyonu ile oldukça benzerdir:
#include “producerConsumer.h”
#include “SignalSlotExample.h”
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
SignalSlotExample w;
w.show();
return a.exec();
}
Aşağıda uygulamamızın temel sınıfı olan SignalSlotExample gösterilmiştir:
#ifndef SIGNALSLOTEXAMPLE_H
#define SIGNALSLOTEXAMPLE_H
#include <QtWidgets/QMainWindow>
#include "ui_SignalSlotExample.h"
#include "producerConsumer.h"
#include
#include <QtCore/QWaitCondition>
class SignalSlotExample : public QMainWindow
{
Q_OBJECT
public:
SignalSlotExample(QWidget *parent = Q_NULLPTR);
private:
Ui::SignalSlotExampleClass ui;
ProducerConsumerInitVariables* pc;
Producer* producer;
Consumer* consumer;
private slots:
void on_writeButton_clicked();
void on_readButton_clicked();
void onStringConsumed(const QString& text);
void onStringSizeInBuffer(int numOfBytesInBuffer);
};
#endif
SignalSlotExample sınıfı üzerinde biraz konuşmamız gerekiyor. Bu sınıfın özel (private) üyeleri Qt Desginer ile tasarlamış olduğumuz arayüz nesnesi, Producer ve Consumer sınıflarının başlangıç parametrelerinin ve ortak değişkenlerinin tutulacağı ProducerConsumerInitVariables sınıfına işaretçi, Producer ve Consumer sınıflarına birer işaretçi içermektedir.
Buton tıklamalarına cevap olarak kullanılacak olan on_writeButton_clicked ve on_readButton_clicked fonksiyonları da yine bu sınıfta tanımlanmış özel slotlardır. Burada dikkat edeceğiniz nokta daha önce connect fonksiyonu ile aşağıda gösterilen bağlantının SignalSlotExampleClass sınıfında belirtilmiş olmasına rağmen on_writeButton_clicked fonksiyonunun tanımının SignalSlotExample sınıfında yapılmış olmasıdır.
QObject::connect(writeButton, SIGNAL(clicked()), SignalSlotExampleClass, SLOT(on_writeButton_clicked()));
Bu kafa karıştırıcı olsa da bu durum Qt’nin sinyal/slot çalışma mantığına uygundur. Burada tıklama sinyali yayınlandığında SignalSlotExampleClass sınıfının sahibi olan SignalSlotExample sınıfında tanımlanmış olan slot tarafından bu sinyal yakalanmaktadır.
Aşağıda SignalSlotExample sınıfının fonksiyon tanımlarını içeren SignalSlotExample.cpp dosyası gösterilmektedir. Bu kodu inceleyiniz devamında gerekli açıklamaları bulacaksınız.
#include "SignalSlotExample.h"
SignalSlotExample::SignalSlotExample(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
this->pc = new ProducerConsumerInitVariables();
this->producer = new Producer(this->pc, this);
this->consumer = new Consumer(this->pc, this);
QObject::connect(this->consumer, SIGNAL(stringConsumed(const QString&)), this, SLOT(onStringConsumed(const QString&)));
QObject::connect(this->producer, SIGNAL(stringSizeInBuffer(int)), this, SLOT(onStringSizeInBuffer(int)));
QObject::connect(this->consumer, SIGNAL(stringSizeInBuffer(int)), this, SLOT(onStringSizeInBuffer(int)));
}
void SignalSlotExample::on_writeButton_clicked()
{
this->producer->start();
}
void SignalSlotExample::on_readButton_clicked()
{
this->consumer->start();
}
void SignalSlotExample::onStringConsumed(const QString& text)
{
ui.bufferBrowser->insertPlainText(text);
}
void SignalSlotExample::onStringSizeInBuffer(int numOfBytesInBuffer)
{
ui.numOfCharBar->setValue(numOfBytesInBuffer);
}
Sınıfın yapıcı fonksiyonunda ilk olarak arayüzün oluşturulması için gerekli setupUi fonksiyonu çağrılıyor. Ardından ProducerConsumerInitVariables fonksiyonu çağrılarak ipliklerde kullanılacak gerekli parametreler ayarlanıyor. Consumer ve Producer iplikleri tanımlanıyor.
Daha sonra 4 tane daha sinyal/slot tanımlandığını görüyorsunuz. Önceki sinyal/slotların aksine bunları kendimiz manuel olarak oluşturduk. Aşağıda örnek olarak incelemek için bir tanesini alıntıladım:
QObject::connect(this->consumer, SIGNAL(stringConsumed(const QString&)), this, SLOT(onStringConsumed(const QString&)));
connect fonksiyonuna verilen ilk parametre sinyalin hangi nesneden kaynaklanacağını gösteriyor. İkinci parametre ise sinyalin ismidir. Sinyalin ismini verirken parametrelerin nesnenin içersindeki tanımlanan sinyal ile uyumlu olması gerektiğini unutmamamız gerekiyor. Yani bu demek oluyor ki stringConsumed sinyali Consumer sınıfından yayınlanacak. Bu konuya ipliklerin kodunu incelerken tekrar değineceğim. Üçüncü parametre ise hangi objenin bu sinyale karşı slotu barındıracağıdır. Bu uygulamada Producer ve Consumer sınıflarından yaratılan nesneler SignalSlotExample sınıfından bir nesne tarafından yakalanacak ve bu slotun ismi de yukarıdaki örnek için onStringConsumed olacaktır.
Diğer sinyal/slot bağlantılarını da bu örnek üzerinden anlamaya çalışabilirsiniz. SignalSlotExample kodunu incelemeye devam ettiğinizde slotların tanımlandığını göreceksiniz.
void SignalSlotExample::on_writeButton_clicked()
{
this->producer->start();
}
Yukarıdaki slot “Yaz” butonuna tıklandığında producer ipliğini başlatır ve bu iplik tampon belleğine bellek dolana kadar yazar.
void SignalSlotExample::on_readButton_clicked()
{
this->consumer->start();
}
Benzer şekilde yukarıdaki slot “Oku” butonuna tıklandığında consumer ipliğini başlatır ve bu iplik tampon belleğine yazılan bilgi olup olmadığını kontrol eder ve eğer önceden yazılmış veri varsa onları okur.
Okuma işlemi devam ettikçe okunan bilgiler kullanıcıya metin tarayıcı üzerine yazılarak gösterilir. Bunun için her sekizli (byte) okunduğunda aşağıdaki slot fonksiyonu çağrılır.
void SignalSlotExample::onStringConsumed(const QString& text)
{
ui.bufferBrowser->insertPlainText(text);
}
Bellekteki doluluk oranının ilerleme çubuğu aracılığı ile takip edileceğini daha önce söylemiştim. Bunun için onStringSizeInBuffer slot fonksiyonu kullanılır. Bu slot fonksiyonu hem producer hem de consumer tarafından çağrılır. Tampon bellekten okunan ya da yazılan her sekizli için ilerleme çubuğu güncellenir.
void SignalSlotExample::onStringSizeInBuffer(int numOfBytesInBuffer)
{
ui.numOfCharBar->setValue(numOfBytesInBuffer);
}
Aşağıda iplik (Producer ve Consumer) tanımlarının yapıldığı ve başlangıç değerlerinin tanımlandığı yapının (producerConsumerInitVariables) yer aldığı producerConsumer.h dosyası gösterilmektedir:
#ifndef PRODUCERCONSUMER_H
#define PRODUCERCONSUMER_H
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
#include <QtCore/QThread>
#include <QtCore/QTime>
#include <QtCore/QDebug>
#define BUFFER_SIZE 256
typedef struct producerConsumerInitVariables {
const int DataSize = 1024;
char buffer[BUFFER_SIZE];;
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;
} ProducerConsumerInitVariables;
class Producer : public QThread
{
Q_OBJECT
public:
Producer(QObject* parent = NULL) : QThread(parent)
{
this->pc = new ProducerConsumerInitVariables();
}
Producer(ProducerConsumerInitVariables *pc, QObject* parent = NULL) : QThread(parent)
{
this->pc = pc;
}
void run() override
{
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
for (int i = 0; i < pc->DataSize; ++i) {
pc->mutex.lock();
if (pc->numUsedBytes == BUFFER_SIZE) {
pc->bufferNotFull.wait(&pc->mutex);
}
pc->mutex.unlock();
pc->buffer[i % BUFFER_SIZE] = "ACGT"[(int)qrand() % 4];
pc->mutex.lock();
++pc->numUsedBytes;
pc->bufferNotEmpty.wakeAll();
pc->mutex.unlock();
emit stringSizeInBuffer(pc->numUsedBytes);
this->msleep((int)qrand() % 50);
}
}
signals:
void stringSizeInBuffer(int);
private:
ProducerConsumerInitVariables* pc;
};
class Consumer : public QThread
{
Q_OBJECT
public:
Consumer(QObject* parent = NULL) : QThread(parent)
{
}
Consumer(ProducerConsumerInitVariables* pc, QObject* parent = NULL) : QThread(parent)
{
this->pc = pc;
}
void run() override
{
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
for (int i = 0; i < pc->DataSize; ++i) {
pc->mutex.lock();
if (pc->numUsedBytes == 0) {
pc->bufferNotEmpty.wait(&pc->mutex);
}
pc->mutex.unlock();
emit stringConsumed(QString(pc->buffer[i % BUFFER_SIZE]));
pc->mutex.lock();
--pc->numUsedBytes;
pc->bufferNotFull.wakeAll();
pc->mutex.unlock();
emit stringSizeInBuffer(pc->numUsedBytes);
this->msleep((int)qrand() % 50);
}
fprintf(stderr, "\n");
}
signals:
void stringConsumed(const QString&);
void stringSizeInBuffer(int);
private:
ProducerConsumerInitVariables* pc;
};
#endif
Bir iplik tanımlama için QThread sınıfından kalıtım almalısınız ve run fonksiyonunu yeniden tanımlamalısınız. İki iplikte de yapıcı fonksiyonlar parametre olarak ipliklerin paylaştığı değişkenleri tanımlayan producerConsumerInitVariables sınıfından bir işaretçi aldığı görülecektir:
typedef struct producerConsumerInitVariables {
const int DataSize = 1024;
char buffer[BUFFER_SIZE];;
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;
} ProducerConsumerInitVariables;
Bu yapı içersinde yazılacak verinin boyutu, tampon bellek (buffer değişkeni), belleğin dolu ya da boş olduğunda ipliğe haber verecek olan bekleme koşulları (wait conditions), karşılıklı dışlama (muteks – ing. mutex) ve belleğin ne kadarının dolu olduğunu tutan değişkenler yer almaktadır.
Aşağıda tampon belleğe yazacak olan Producer sınıfı gösterilmektedir.
class Producer : public QThread
{
Q_OBJECT
public:
Producer(QObject* parent = NULL) : QThread(parent)
{
this->pc = new ProducerConsumerInitVariables();
}
Producer(ProducerConsumerInitVariables *pc, QObject* parent = NULL) : QThread(parent)
{
this->pc = pc;
}
void run() override
{
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
for (int i = 0; i < pc->DataSize; ++i) {
pc->mutex.lock();
if (pc->numUsedBytes == BUFFER_SIZE) {
pc->bufferNotFull.wait(&pc->mutex);
}
pc->mutex.unlock();
pc->buffer[i % BUFFER_SIZE] = "ACGT"[(int)qrand() % 4];
pc->mutex.lock();
++pc->numUsedBytes;
pc->bufferNotEmpty.wakeAll();
pc->mutex.unlock();
emit stringSizeInBuffer(pc->numUsedBytes);
this->msleep((int)qrand() % 50);
}
}
signals:
void stringSizeInBuffer(int);
private:
ProducerConsumerInitVariables* pc;
};
Bu sınıfta yer alan fonksiyonlar için gerekli tanımlamalar sınıf içerisinde yer almaktadır. Sınıfın yapıcı fonksiyonu başlangıç yapısını işaretçi ile almaktadır. run fonksiyonu ise daha önce gördüğümüz ilgili butona basıldığında çalışacak olan slot fonksiyonu tarafından başlatılacak fonksiyondur. Yani bu uygulamada kullanıcı “Yaz” butonuna basacak bu sinyal on_writeButton_clicked slotunu çalıştıracak bu slot da producer->start() fonksiyonu ile ipliği başlatacaktır. Bu iplik içerisinde ayrıca stringSizeInBuffer sinyali tanımlanmıştır. Bu sinyal de her sekizli eklendiğinde çalışacak ve ilerleme çubuğu doldurulacaktır. Aynı sinyal Consumer sınıfında da tanımlanmıştır. Consumer sınıfı da bellekten okuma yaptıkça ilerleme çubuğunu azaltacaktır. Çünkü okuma yaptıkça Producer ipliği okunan bölgeleri kullanabilecektir.
Burada bir diğer önemli yapı da bekleme koşullarıdır. Aşağıda gösterilen yapı “eğer bellek doluysa bu ipliği beklet” demektedir. Bunun için muteksin bekleme koşuluna parametre olarak verildiğine dikkat etmişsinizdir. Aşağıda muteksler hakkında bilgi verdikten sonra konuya devam edelim.
pc->mutex.lock();
if (pc->numUsedBytes == BUFFER_SIZE) {
pc->bufferNotFull.wait(&pc->mutex);
}
pc->mutex.unlock();
Muteksler iki ya da daha çok ipliğin belli bir bellek alanına ulaşımını sınırlandıran ve bu şekilde çalışmalarını düzenlememize izin veren yapılardır. Burada kullanılan bellek miktarını kontrol ettiğimiz sırada diğer iplik tarafından numUsedBytes değişkeninin değiştirilmesini engellemek istiyoruz. Değişkeni kontrol ettikten sonra eğer bellek doluysa ipliği bekletmemiz lazım fakat aynı zamanda muteksi de serbest bırakmamız gerekiyor yoksa diğer iplik de çalışmaya devam edemez. Bunun için Qt, QWaitCondition sınıfında wait fonksiyonunu sağlıyor. Bu fonksiyon çağrıldığında muteksi açıyor (unlock) ve kendi ipliğini durduruyor. Böylece diğer iplik çalışmaya devam edebilir hale geliyor. Ardından çalışan iplik kendi işini halledip bu ipliğin tekrar başlaması için sinyal yaydığında muteks tekrar kilitlenecek (lock) ve mutex.unlock() komutunu görünceye kadar ilgili alan kilitli kalacaktır.
Consumer sınıfı ise aşağıda gösterilmiştir:
class Consumer : public QThread
{
Q_OBJECT
public:
Consumer(QObject* parent = NULL) : QThread(parent)
{
}
Consumer(ProducerConsumerInitVariables* pc, QObject* parent = NULL) : QThread(parent)
{
this->pc = pc;
}
void run() override
{
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
for (int i = 0; i < pc->DataSize; ++i) {
pc->mutex.lock();
if (pc->numUsedBytes == 0) {
pc->bufferNotEmpty.wait(&pc->mutex);
}
pc->mutex.unlock();
emit stringConsumed(QString(pc->buffer[i % BUFFER_SIZE]));
pc->mutex.lock();
--pc->numUsedBytes;
pc->bufferNotFull.wakeAll();
pc->mutex.unlock();
emit stringSizeInBuffer(pc->numUsedBytes);
this->msleep((int)qrand() % 50);
}
fprintf(stderr, "\n");
}
signals:
void stringConsumed(const QString&);
void stringSizeInBuffer(int);
private:
ProducerConsumerInitVariables* pc;
};
#endif
Bu sınıf içersinde de benzer bir muteks yapısı yer almaktadır. Bu kısmı kodu incelediğiniz takdirde çok rahat bir şekilde anlayacağınızı tahmin ediyorum. Burada yine Producer sınıfında da bulunan bir yapıyı bu sefer consumer sınıfında görelim. Daha önce eğer bellek doluysa Producer ipliğinin belleğe yazmayı bıraktığını ve beklemeye geçtiğini görmüştük. Peki Producer sınıfı bu beklemeden nasıl kurtulacak ve çalışmaya devam edecek? Burada Consumer sınıfında yer alan aşağıdaki yapı çok önemli:
pc->mutex.lock();
--pc->numUsedBytes;
pc->bufferNotFull.wakeAll();
pc->mutex.unlock();
Burada yine muteks kullanılıyor çünkü numUsedBytes değişkenine aynı anda diğer iplik tarafından erişilmesini istemiyoruz. Bu değişkeni azalttıktan sonra artık bufferNotFull bekleme durumu içersinde yer alan wakeAll fonksiyonunu kullanarak diğer ipliğin çalışmaya başlamasını sağlayabiliriz. Bu fonksiyon daha önce durdurulan ipliklerin çalışmaya başlamasını söyleyecektir çünkü bellekte artık yer açılmış oldu.
İpliklerin sonunda kullandığımız sleep fonksiyonları dikkatinizi çekmiştir:
this->msleep((int)qrand() % 50);
Bu fonksiyonu bir bilgisayarın tampon belleğine gelen verileri daha iyi simüle etmek için kullandım. Hem de bu şekilde ilerleme çubuğunda yaşanan değişimler daha net gözlenmektedir. (int)qrand() % 50 , ile 0 ile 50 arasında bir sayı rasgele üretilmektedir. Yazımız bu sefer biraz uzun oldu ama umarım okuyan herkes için faydalı olur.