基于互斥量的线程同步

最近在阅读Qt 5.9 C++开发指南,为了加深对书本上内容的理解,参照书上的讲解尝试写了一些demo,用于以后工作中查阅,如果涉及侵权请告知实例程序samp13_2

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>
#include <QMutexLocker>

class MyThread : public QThread
{
    Q_OBJECT

private:
    int _times;
    int _value;
    QMutex _mutex;

protected:
    void run();

public:
    MyThread();
    ~MyThread();
    bool timesValue(int *times, int *value);

};

#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include <QRandomGenerator>

MyThread::MyThread()
{
    _times = 0;
    _value = 0;
}

MyThread::~MyThread()
{

}

// 现在通过函数的方式与主窗口交互
// 首先要理解线程同步,简单说,就是主线程想要获取子线程的数据,要保证获取到的数据是同步的,说白了,就是主线程第一次获取数据要是子线程第一次产生的数据,
// 不能是子线程第二次产生的数据,那这就叫线程同步,实现线程同步方法很多,先这个demo杰少互斥量
// 什么是互斥量,简单说,就是人家在动的东西你不能动,比如人家在上厕所,那么你就不能进去,你进去就凉凉
// 互斥量有几个类可以实现QMutex QMutexLocker,他们的区别就是上完厕所需不需要手动冲厕所
//方法1
//void MyThread::run()
//{
//    while(1)
//    {
//        // 方法1,用QMutex
//        _mutex.lock(); // 锁上

//        _value = QRandomGenerator::global()->generate(); // 用这个类生成随机数
//        _value = (_value % 6) + 1;

//        // 互斥量只能锁一次,如果这里continu就会出错
//        // 这个类生成的数可能是负数,如果是负数就从新生成
//        if(_value <= 0)
//        {
//            _mutex.unlock();
//            continue;
//        }

//        _times = _times + 1;
//        _mutex.unlock(); // 打开锁

//        msleep(500); // 先睡一会儿在生成数字
//    }
//}

//方法2
void MyThread::run()
{
    while(1)
    {
        // 方法2,用QMutexLocker
        if(1)
        {
            // 这个类包裹一下,书上说,会在生命周期外自动析构,操作了下,感觉不一定
            // 这个demo用qtimer定时读取数据,很可能线程不同步,通过不停的结束游戏开始游戏,就会出现这种情况,但是不用这个类就不会出现这种情况???
            QMutexLocker mutexLocker(&_mutex);
            _value = QRandomGenerator::global()->generate(); // 用这个类生成随机数
            _value = (_value % 6) + 1;

            // 互斥量只能锁一次,如果这里continu就会出错
            // 这个类生成的数可能是负数,如果是负数就从新生成
            if(_value <= 0)
            {
                continue;
            }

            _times = _times + 1;
        }

        msleep(500); // 先睡一会儿在生成数字
    }
}

bool MyThread::timesValue(int *times, int *value)
{
    if(_mutex.tryLock())
    {
        *times = _times;
        *value = _value;
        _mutex.unlock();
        return true;
    }

    return false;
}

mydialog.h

#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <mythread.h>
#include <QDialog>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QTimer>

class MyDialog : public QDialog
{
    Q_OBJECT

private:           
    QPushButton *_btnStart;
    QPushButton *_btnFinish;
    QPushButton *_btnClearInfo;
    QPlainTextEdit *_plainTextEdit;
    QLabel *_labStatus;
    QLabel *_labPicture;
    MyThread *_thread;
    QTimer *_timer;
    int _times;

protected:
    void closeEvent(QCloseEvent *event);

public:
    MyDialog(QWidget *parent = nullptr);
    ~MyDialog();

    void iniUI();
    void iniSignalSlots();

private slots:
    void btnStartClicked();
    void btnFinishClicked();
    void btnClearInfoClicked();
    void timerTimeOut();

};

#endif // MYDIALOG_H

mydialog.cpp

#include "mydialog.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QPixmap>
#include <QString>

MyDialog::MyDialog(QWidget *parent) : QDialog(parent)
{
    iniUI();

    // 创建线程
    _thread = new MyThread();
    // 创建定时器
    _timer = new QTimer();
    // 初始化次数
    _times = 0;

    iniSignalSlots();
}

MyDialog::~MyDialog()
{

}

// 初始化界面
void MyDialog::iniUI()
{
    // 创建按钮
    _btnStart = new QPushButton("启动游戏");
    _btnFinish = new QPushButton("结束游戏");
    _btnFinish->setEnabled(false);
    _btnClearInfo = new QPushButton("清空文本");

    // 布局按钮
    QHBoxLayout *layout1 = new QHBoxLayout();
    layout1->addWidget(_btnStart);
    layout1->addWidget(_btnFinish);
    layout1->addWidget(_btnClearInfo);

    // 创建文本框和图片显示框
    _plainTextEdit = new QPlainTextEdit();
    _labPicture = new QLabel();
    QPixmap pixmap;
    pixmap.load(":/images/d0.jpg");
    _labPicture->setPixmap(pixmap);

    // 布局文本框和图片显示框
    QHBoxLayout *layout2 = new QHBoxLayout();
    layout2->addWidget(_plainTextEdit);
    layout2->addWidget(_labPicture);

    // 创建状态栏
    _labStatus = new QLabel("游戏状态:未开始");

    // 布局状态栏
    QHBoxLayout *layout3 = new  QHBoxLayout();
    layout3->addWidget(_labStatus);

    // 创建分组框的布局
    QVBoxLayout *layout4 = new QVBoxLayout();
    layout4->addLayout(layout1);
    layout4->addLayout(layout2);
    layout4->addLayout(layout3);

    // 创建一个分组框
    QGroupBox *groupBox = new QGroupBox("掷骰子");
    groupBox->setLayout(layout4);

    // 创建整体布局
    QHBoxLayout *layout5 = new QHBoxLayout();
    layout5->addWidget(groupBox);

    // 设置整体布局
    setLayout(layout5);
    resize(800, 600);
}

// 连接信号与槽
void MyDialog::iniSignalSlots()
{
    connect(_btnStart, SIGNAL(clicked()), this, SLOT(btnStartClicked()));
    connect(_btnFinish, SIGNAL(clicked()), this, SLOT(btnFinishClicked()));
    connect(_btnClearInfo, SIGNAL(clicked()), this, SLOT(btnClearInfoClicked()));
    connect(_timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));

}

void MyDialog::btnStartClicked()
{
    _labStatus->setText("游戏状态:进行中");
    _thread->start();
    _btnStart->setEnabled(false);
    _btnFinish->setEnabled(true);
    _timer->start(100);
}

void MyDialog::btnFinishClicked()
{
    _labStatus->setText("游戏状态:未开始");
    _thread->terminate();
    _thread->wait();
    _btnStart->setEnabled(true);
    _btnFinish->setEnabled(false);
    _timer->stop();
}

void MyDialog::btnClearInfoClicked()
{
    _plainTextEdit->clear();
}

void MyDialog::closeEvent(QCloseEvent *event)
{
    if(_thread->isRunning()) // 如果线程还在继续执行,那么强行终止
    {
        _thread->terminate();
        _thread->wait();
    }
    event->accept();
}

void MyDialog::timerTimeOut()
{
    int times = 0;
    int value = 0;
    bool valid = _thread->timesValue(&times, &value);

//    if(valid && times != _times) // 这里的设置决定线程是否同步,times == _times + 1
    if(valid && times == _times + 1) // 这里的设置决定线程是否同步,times == _times + 1,但是万一有一次没接受到,游戏就结束了
    {
        _times = times;

        QString text = QString::asprintf("第%d次掷骰子,点数为:%d", times, value);
        _plainTextEdit->appendPlainText(text);
        QString filename = QString::asprintf(":/images/d%d.jpg", value);
        QPixmap pixmap;
        pixmap.load(filename);
        _labPicture->setPixmap(pixmap); // 给一个label设置图片
    }
}

main.cpp

#include "mydialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyDialog w;
    w.show();

    return a.exec();
}

效果展示

猜你喜欢

转载自www.cnblogs.com/samp000/p/12354111.html