QT基于互斥锁的线程同步

多线程这个东西越接触越觉得他的强大,我高中的时候就希望自己有几个脑袋,一个看电视,一个玩,一个写作业,一心多用,这个多线程不正好就是的,本次主要是对基于互斥锁的线程同步的研究,显示最基本的互斥锁。

首先是互斥锁的概念,他是来源于生产者/消费者(producer/consumer)模型,比如有一个分线程是负责生产,主线程是消费者,从分线程获取资源,互斥锁 是为了让分线程生产好了一个东西以后再让主线程来获取。

互斥锁有两个类QMutex和QMutexLocker,其中QMutex的实例就是互斥量。QMutex常用的函数有:

lock()锁定互斥量
unlock()解锁一个互斥量
trylock()尝试锁一个互斥量,不行就算了

我个人觉得这就跟指针一样,自己上锁解锁比较容易出错或者忘了,可以像智能指针一样,让他完成以后自己解锁,这就是QMutexLocker的作用。把互斥量作为参数传给QMutexLocker就行了,系统会在分线程完成一次生产后让主线程接受。

读写锁和等待

接下来就是性能优化的问题了这里提出了读写锁的概念,互斥量本身其实是影响程序的性能的,他在生产的过程中会造成堵塞,其他的线程都要等着,应该允许让多个线程访问互斥量。

QReadWritelock主要函数
LockForRead()以只读的方式锁定资源
LockForWrite()以只写的方式锁定资源
unlock()解锁
tryLockForRead()LockForRead()的非阻塞版本
tryLockForWrite()LockForWrite()的非阻塞版本

QReadLocker和QWriteLocker和前面互斥量的QMutexLocker一样,绑定以后不需要手动解锁上锁操作。

基于互斥量的线程同步有几个问题:

第一个问题就是一个线程资源释放了以后,不能及时的通知其他线程

这里引入了QWaitCondition,他的主要函数:

wait(QMutex *lockedmutex)进入等待状态,解锁互斥量lockedmutex,被唤醒后锁定lockedmutex并退出函数
wakeAll()唤醒所有处于等待的线程,唤醒顺序不确定,这个看系统的调度策略
wakeOne()唤醒一个处于等待的线程,唤醒哪一个不确定,这个看系统的调度策略

QWaitCondition一般用于生产者/消费者模型,前面说过了,模型视图如图:

(线程之间的通讯和之前一样采用信号槽的方式)

实例这里我自己没写,就看看书上的源码(Qt6 C++开发指南)

参考上面的模型做的,创建了3个线程,一个生产者TDiceThread,两个消费者TValueThread(获取点数)和TPictureThread(根据获取点数生成图片文件名)

有两个全局变量seq(步数)和dicevalue(点数)

int seq=0, diceValue=0;//seq步数,dicevalue点数

定义实现3个上述的线程

//TDiceThread 是产生骰子点数的线程
class TDiceThread : public QThread
{
    Q_OBJECT
protected:
    void    run();      //线程的任务函数
public:
    explicit TDiceThread(QObject *parent = nullptr);
};


//TValueThread 获取骰子点数
class TValueThread : public QThread
{
    Q_OBJECT
protected:
    void    run();      //线程的任务函数
public:
    explicit TValueThread(QObject *parent = nullptr);
signals:
    void  newValue(int seq, int diceValue);
};


//TPictureThread获取骰子点数,生成对应的图片文件名
class TPictureThread : public QThread
{
    Q_OBJECT
protected:
    void    run();      //线程的任务函数
public:
    explicit TPictureThread(QObject *parent = nullptr);
signals:
    void  newPicture(QString &picName);
};

重写run函数:

void TDiceThread::run()//生产者的run函数重写
{//线程的任务函数
    seq=0;
    while(1)
    {
        rwLocker.lockForWrite();    //以写方式锁定
        diceValue = QRandomGenerator::global()->bounded(1,7);  //产生随机数[1,6]
        seq++;
        rwLocker.unlock();          //解锁
        waiter.wakeAll();       //唤醒其他等待的线程
        msleep(500);    //线程休眠500ms
    }
}

void TValueThread::run()//获取点数的run函数重写
{
    while(1)
    {
        rwLocker.lockForRead();     //以只读方式锁定
        waiter.wait(&rwLocker);     //等待被唤醒
        emit  newValue(seq,diceValue);
        rwLocker.unlock();          //解锁
    }
}
void TPictureThread::run()//通过获取点数生成对应图片文件名的run函数重写
{
    while(1)
    {
        rwLocker.lockForRead();     //以只读方式锁定
        waiter.wait(&rwLocker);     //等待被唤醒
        QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);
        emit  newPicture(filename);
        rwLocker.unlock();          //解锁
    }
}

创建线程并挂到对象树上:

mainwindow.h

private:
    TDiceThread     *threadA;       //producer
    TValueThread    *threadValue;   //consumer 1
    TPictureThread  *threadPic;     //consumer 2

mainwindow.cpp

    threadA= new TDiceThread(this);    //producer
    threadValue= new TValueThread(this);   //consumer 1
    threadPic= new TPictureThread(this);     //consumer 2

线程之间使用信号槽机制通讯:

mainwindow.h

private slots:
    void    do_threadA_started();
    void    do_threadA_finished();

    void    do_newValue(int seq, int diceValue);
    void    do_newPicture(QString &picName);

mainwindow.cpp

    connect(threadA,&TDiceThread::started, this, &MainWindow::do_threadA_started);
    connect(threadA,&TDiceThread::finished,this, &MainWindow::do_threadA_finished);

    connect(threadValue,&TValueThread::newValue,this, &MainWindow::do_newValue);
    connect(threadPic,&TPictureThread::newPicture,this, &MainWindow::do_newPicture);

void MainWindow::do_threadA_started()
{//与线程的started()信号关联
    ui->statusbar->showMessage("Thread状态:thread started");
    ui->actThread_Run->setEnabled(false);
    ui->actThread_Quit->setEnabled(true);
}

void MainWindow::do_threadA_finished()
{//与线程的finished()信号关联
    ui->statusbar->showMessage("Thread状态:thread finished");
    ui->actThread_Run->setEnabled(true);
    ui->actThread_Quit->setEnabled(false);
}

void MainWindow::do_newValue(int seq, int diceValue)
{
    QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);
    ui->plainTextEdit->appendPlainText(str);
}

void MainWindow::do_newPicture(QString &picName)
{
    QPixmap pic(picName);
    ui->labPic->setPixmap(pic);
}

另外为了避免线程还没结束就关闭程序,要重写一下wediget的closeevent函数:

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (threadA->isRunning())
    {
        threadA->terminate();   //强制结束线程
        threadA->wait();        //等待线程结束
    }

    if (threadValue->isRunning())
    {
        threadValue->terminate();   //强制结束线程
        threadValue->wait();        //等待线程结束
    }

    if (threadPic->isRunning())
    {
        threadPic->terminate();   //强制结束线程
        threadPic->wait();        //等待线程结束
    }

    event->accept();
}

关闭窗口的时候检查三个线程是不是还在运行,如果在运行就强制关闭。 

这样程序大体就完成了,详见源程序 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值