التزامن(2)
لقد تكلمنا عن التزامن في مقال سابق ولكن مع Qmutex لقد تعرفنا كيف يمكن قفله وكيف يمكن فتحه هي بذلك تمثل اشارة مرور للمعالج.
حديثنا اليوم يشابه لحد بعيد حديث الأمس ولكن م QReadWriteLock انت في الحقيقة لا تعرف ماذا يقرأ وماذا يكتب ولكن كل ماعليك معرفته ان اقفال الكتابة يقفل الوصول بواسطة القراءة والكتابة ولكن اقفال القارئ يقفل الوصول للكتابة فقط وهذا ما سوف تلاحظه في المثال القادم .
ملاحظة: اذا كانت العملية سوف توقف المعالج فلا تستخدم الدالة wait للمعالج للإقفال لأن البرنامج لن يقفل وقتها .
وهناك صفوف تستقبل مؤشر ل QReadWriteLock وتسمى بصفوفة الإقفال المؤقت بمجرد تدمير احد هذه الصفوف سوف يفتح القاراءة والكتابة بشكل تلقائي وهي QWriteLocker & QReadLocker
عموما سوف يوضح لك المثال أمورا أكثر ولكن لماذا نحتاج الى هكذا عمل ؟؟؟؟؟
دعنا نفرض انك انشأت متغير ولديك معالجين الأول للقراءة والآخر للكتابة فإنك من الطبيعي سوف تطلب الكتابة على المتغير قبل قراءته فلذلك عند اقفال الكتابة يتم اقفال القراءة وثم ان القراءة سوف تقفل الكتابة لأنك لاتريد ان يحدث اضافة للمتغير اثناء قراءتك له مما قد يغير من نتائجك ان هذه هي الفكرة العامة ولن تضح أكثر الا عندما تطلع على المثال التالي
#include <QApplication>
#include <QtGui>
#include <QtCore>
//================================================== ========//
QReadWriteLock lock;
int m_int=0;
bool stopThreads = false;
//================================================== ========//
class TextThread : public QThread
{public:void run();};
//================================================== ========//
void TextThread::run(){
while(!stopThreads){
lock.lockForRead();
qDebug() << QString("%1").arg(m_int);
sleep( 1 );
lock.unlock();
}
}
//================================================== ========//
class TextThread1 : public QThread
{public:void run();};
//================================================== ========//
void TextThread1::run(){
while(!stopThreads){
lock.lockForWrite();
m_int++;
sleep( 1 );
lock.unlock();
msleep(1);
}
}
//================================================== ========//
int main( int argc, char **argv )
{
QApplication app( argc, argv );
TextThread foo;
TextThread1 bar;
foo.start();
bar.start();
QMessageBox::information( 0, "Threading", "Close me to stop!" );
stopThreads = true;
foo.wait();
bar.wait();
return 0;
}
وكان ناتج هذه العملية هو
"1"
"2"
"3"
"4"
"5"
"6"
"7"
إلى توقف البرنامج
تخيل لو أن قمنا بحذف هذا الكود
QReadWriteLock lock;
ستجد إن النواتج غير متدرجة ومنظمة وخاصة سرعة الكتابة في المتغيرات أسرع من القراءة منه وتحريره على ال console .
حساب نظام semaphore))
لقد تحدثنا عن mutex وذكرنا فوائده ثم انتقلنا ال write and read lock وقد فصلنا فيه بما يكفي
ولكن Qt لا تقتنع بذلك فهي تريد ان توفر كل شيئ من اجل عملية معالجة مميزة فصنعت نظام semaphore
والذي هو اشبه بعملية مرسل ومستقبل المرسل اما ان يرسل نصوص او ارقام او أي شيئ آخر والمستقبل بدوره لا يحتاج ان ينتظر فهو يتلقى حتى يبقى 0 عنصر دالخل semaphore وكالعادة الأمثلة هي خير سبيل للتوضيح
قبل ان نذكر مثال يجب ان نعرف بأهم الدوال
Acquier تقوم بإنقاص واحد من semaphore في الحالة الطبيعية ان لم يضف الى وسيطه قيمة اخرى.
Release تقوم بإضافة واحد الى semaphore في الحالة الطبيعية ان لم يضف الى وسيطه قيمة اخرى.
tryAcquier هي تحاول ان تنقص من semaphore الكمية التي تريده خلال زمن تحدده انت ان لم تستطع بسبب ان الكمية التي طلبتها اكبر من المتوفر تعيد القيمة false .
المثال :
const int bufferSize = 20;
QChar buffer[ bufferSize ];
QSemaphore freeSpace( bufferSize );
QSemaphore availableData( 0 );
bool atEnd = false;
QString m_text="mohammed alabdaly is from jeddah";
هذه المتغيرات جميعها عامة وأهم مافيها هو المتغيرين
QSemaphore freeSpace( bufferSize );
QSemaphore availableData( 0 );
وهما خلاصة شرحنا للمثال دعنا نكمل الآن وثم توضح الصورة في ذهنك أكثر
class TextProducer:public QThread{public:void run();};
//================================================== ========//
class TextConsumer:public QThread{public:void run();};
صنعنا صفين الأول للإنتاج والثاني للإستهلاك وكلاهم يحويان ادالة
Run
والتي تعمل بجرد استدعاء الأمر
Start
C++ كود
void TextProducer::run(){
for( int i=0; i<m_text.length(); ++i ){
freeSpace.acquire();
buffer[ i % bufferSize ] = m_text[ i ];
if( i == m_text.length()-1 )atEnd = true;
availableData.release();}}
في هذا الأمر قمنا بعملية التكرار أولا وهي من 0 الى اخر حرف في المتغير m_text عموما
قمنا بإنقاص واحد من freeSpace في كل مرة في عملية التكرار ثم قمنا بتخزين الحرف في buffer وبما انه
ثم اذا وصل عدد مرات التكرار الى عدد الحروف فإنه يرسل القيمة true الى المتغير atEnd ويتم اضافة واحد الى availableData .
في الكود السابق هناك تعبيرات يجب ان تعرفها مثل buffer[ i % bufferSize ] وهذه الطريقة لتوفير مساحة فيضمن ان الحرف سوف يخزن في عناصر لن يزيد عن المساحة المسموح بها وهي 19 .
void TextConsumer::run(){
int i = 0;
while( !atEnd || availableData.available() ){
availableData.acquire();
qDebug() << buffer[ i ];
i = (i+1) % bufferSize;
freeSpace.release();}}
وهذه دالة للمستهلك في حال لم يصل المنتج الى النهاية او انتج كمية جديدة من الحروف غير مستهلكة فإن عملية التكرار مستمرة ثم يتم انقاص واحد من availableDataويتم تحرير الحرف المنتج في console وأضفنا للمتغير i واحد وإذا تجاوز الرقم 20 فإنه يبدأ العد من جديد حسب نظرية الباقي لكي يواكب الإنتاج ان كان كمياته كبيرة وفي النهاية تم زيادة
freeSpace واحد ل يرجع لقيمته الأصلية وهي 20 .
int main( int argc, char **argv ){
QApplication app( argc, argv );
TextProducer producer;
TextConsumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;}
وهذه الدالة main جرب المثال السابق واختبر الناتج وسوف يكون عبارة عن حروف النص
mohammed alabdaly is from jeddah .
حتى انهي الموضوع من جميع جوانبه هناك دوال في Qobject تساعدك في المعالجة وهي
moveToThread وهو لنقل العنصر لمعالج آخر مع استثناء العناصر التي لها اباء فإنك لايمكن نقلها لأنها تعمل تحت أمر الأب فإذا تم اغلاق الأب تغلق جميع النوافذ بداخله .
كيف تعرف المعالج الرئيسي ؟
العالج الرئيسي هو المعالج ل Qapplication وإليك طريقة استخراجه
QApplication::instance()->thread()
عموما الدالة thread ايضا هي مأخوذة من من الصف Qobject ويعطيك المعالج للعنصر .
اخوكم محمد العبدلي رئيس فؤق مصفوفة مشرف في موقع qt-ar
اتمنى انكم استفدتم من سلسلة المعالجة في Qt وشكركم لي هو الدعاء وفي امان الله