Qt中的多线程及其应用(2)

五、银行家算法的分析和实现

问题描述:研究一个银行家如何将总数一定的资金,安全地借给若干个客户,是客户既能满足资金需求,也能使银行家收回本金。

限制条件:

 1、每个客户再见款前都必须提前说明所需资金总额;

2、每次借钱都是以一个单位(一万元)进行;

3、客户在拿到一个单位的借款前可能需要等待;

4、银行保证客户的等待时间是有限的(借或不借)。


算法策略:将资金优先借予资金需求较少的客户。(可以尽快归还)

应用场景:操作系统内核中的进程管理;数据库内核中的频繁事务管理。

Qt中的算法实现方案:

使用多线程机制模拟客户和银行;银行优先分配资源给最小需求的客户;当客户资源需求无法满足的时候,收回已分配的资源,强制结束线程(资金安全/异常安全)

编程示例如下:

#include <QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>
#include <QList>

class Customer : public QThread
{
protected:
    int m_need; //资金总额
    volatile int m_current;  //每次都从内存中去读取变量的值 不需要编译器优化    目前手上拥有的资金数量
    QMutex m_mutex;

    void run()
    {
        bool condition = false;

        qDebug() << objectName() << "begin to apply money ...";

        do
        {
            m_mutex.lock();

            condition = (m_current < m_need);

            m_mutex.unlock();

            msleep(10);
        }
        while ( condition );

        qDebug() << objectName() << "end (get enough money)";
    }

public:
    Customer(int current, int need)
    {
       m_current = current;
       m_need = need;
    }

    int backMoney()
    {
        int ret = 0;

        m_mutex.lock();

        ret = m_current;

        m_current = 0;

        m_mutex.unlock();

        return ret;
    }

    void addMoney(int m)
    {
        m_mutex.lock();
        m_current += m;
        m_mutex.unlock();
    }

    int current()
    {
        int ret = 0;

        m_mutex.lock();

        ret = m_current;

        m_mutex.unlock();

        return ret;
    }

    int need()
    {
        return m_need;
    }
};

class Bank : public QThread
{
protected:
    QList<Customer*> m_list;
    int m_total;

    void run()
    {
        int index = -1;

        qDebug() << objectName() << "begin : " << m_total;

        do
        {
            index = -1;

            for(int i=0; i<m_list.count(); i++)
            {
                if( m_list[i]->current() == m_list[i]->need() ) //满足借款后收回
                {
                    qDebug() << objectName() << "take back money from : " << m_list[i]->objectName() << " " << m_list[i]->need();

                    m_total += m_list[i]->backMoney();
                }
            }

            qDebug() << objectName() << "current: " << m_total;  //当前金额

            int toGet = 0x00FFFFFF;

            for(int i=0; i<m_list.count(); i++)  //得到需求最小的m_list[i]
            {
                if( m_list[i]->isRunning() )
                {
                    int tmp = m_list[i]->need() - m_list[i]->current();

                    if( toGet > tmp )
                    {
                        index = i;
                        toGet = tmp;
                    }
                }
            }

            if( index >= 0)
            {
                if( toGet <= m_total )
                {
                   qDebug() << objectName() << "give money to :  " << m_list[index]->objectName();

                   m_total--;

                   m_list[index]->addMoney(1);
                }
                else
                {
                    qDebug() << objectName() << "terminate:  " << m_list[index]->objectName();

                    m_total += m_list[index]->backMoney();

                    m_list[index]->terminate();
                }
            }

            sleep(1);

        }while( index >= 0);

        qDebug() << objectName() << " end: " << m_total;
    }

public:
    Bank(int total)
    {
        m_total = total;
    }

    void addCustomer(Customer* customer)
    {
        m_list.append(customer);
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Customer p(4, 8);
    Customer q(2, 3);
    Customer r(2, 9);

    Bank bank(2);

    p.setObjectName("P");
    q.setObjectName("Q");
    r.setObjectName("R");

    bank.setObjectName("Bank");

    bank.addCustomer(&p);
    bank.addCustomer(&q);
    bank.addCustomer(&r);

    p.start();
    q.start();
    r.start();

    bank.start();

    return a.exec();
}

银行家算法常用于资源分配的场合,通过对需求总量、需求量进行比较,安排合适的资源分配顺序,并且通过及时收回,保证资源的安全,策略就是优先选择需求量较少的“客户”,然后及时对“借出去”的资源进行收回,再分配给次一级需求量的“客户”,最大程度保证每个“客户”都能申请到需求的资源、

六、多线程中的信号与槽

线程对象是否可以发射信号(signal)?是否可以定义槽函数(slot)?

QThread类拥有发射信号和定义槽函数的能力。关键信号如下:

void started(): 线程开始运行时发射该信号

void finished():线程完成运行时发射该信号

void terminated():线程被异常终止时发生该信号

如果程序中有多个线程,槽函数是在哪个线程中被执行的?

进程中存在栈空间的概念,与栈数据结构不同,栈空间专门同于函数调用(保存函数参数、局部变量等),每个线程都拥有独立的栈空间(可调用其他函数)。

只要函数体中没有访问临界资源的代码,同一个函数可以被多个线程同时调用,且不会产生任何副作用。

操作系统通过整型标识管理进程和线程:

进程拥有全局唯一的ID值——PID;

线程拥有进程内唯一的ID值——TID;

通过QThread中的关键静态成员函数,可以得到上述两个ID值;

QThread *currentThread();

Qt::HANDLE currentThreadId();

通过实验来实现多线程的信号与槽。

定义一个线程类,其中包含一个信号和一个槽函数:

signals:
    void testSignal();
protected slots:
    void testSlot();

TestThread::TestThread(QObject *parent) : QThread(parent)
{
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot()));
}

void TestThread::run()
{
    qDebug() << "void TestThread::run() -- begin tid" << QThread::currentThreadId();

    for(int i=0; i<5; i++)
    {
        qDebug() << "void TestThread::run" << i << QThread::currentThreadId();

        sleep(1);
    }

    emit testSignal();
 //exec();
  qDebug() << "void TestThread::run() -- end tid" << QThread::currentThreadId();
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot tid" << QThread::currentThreadId();
}

定义一个Object类,实现三个槽函数:

void MyObject::getStarted()
{
    qDebug() << "void MyObject::getStarted() tid" << QThread::currentThreadId();
}

void MyObject::getFinished()
{
    qDebug() << "void MyObject::getFinished() tid" << QThread::currentThreadId();
}

void MyObject::getTerminate()
{
    qDebug() << "void MyObject::getTerminate() tid" << QThread::currentThreadId();
}

在主函数中,定义一个线程对象,连接线程信号与槽函数:

    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread tt;
    MyObject m;
    //下面连接中的槽函数都在主线程中被调用执行
    QObject::connect(&tt, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&tt, SIGNAL(finished()), &m, SLOT(getFinished()));
    QObject::connect(&tt, SIGNAL(terminated()), &m, SLOT(getTerminate()));

    tt.start();

运行结果如下:



可以看出,main()函数运行后,线程对象开始调用run()函数,在run()函数中打印5个数,然后发送一个testSignal信号,通过构造函数中的信号与槽,调用testslot(),打印输出语句。run()结束后,finished()信号触发getfinished()函数,打印输出语句。

打印的同时,对各个线程的PID也进行的打印。可以发现,在main()线程的PID和在线程类中实现的线程的PID是不一样,也就是说这是两个不同的线程。

槽函数是线程类中的成员时,为什么依然不再本线程内被调用执行?

对象依附于哪一个线程?对象的依附性与槽函数执行的关系?对象的依附性是否可以改变?

默认情况下,对象依附于自身被创建的线程,如上例中的tt、m,都是在main()函数中创建的,所以依附于主线程。

默认的情况下,槽函数在其所依附的线程中被调用执行。

对象的依附性是可以改变的。Qt中通过QObject::moveToThread来改变对象的线程依附性,是的对象的槽函数在依附的线程中被调用执行。

将上面实例中main()函数改为:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    QObject::connect(&t, SIGNAL(started()), &m, SLOT(getStarted()));
    QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()));

    m.moveToThread(&t);
    t.moveToThread(&t);

    t.start();

    return a.exec();
}

即将对象m、t的线程依附性,使其都依附于对象t运行的线程。执行结果如下:


如果不将对象t的线程依附性也依附于t自己呢?

t.start()执行后,会在run()函数中发射信号,构造函数收到这个信号后,调用testSlot()打印语句,这个是一个新的线程,所以可以看到,如果不改变依附性,这个子线程的PID与main()的PID是一样的。


需要注意的版本问题:

在TestThread::run()函数中,有一条被注释的语句:

 //exec();

在Qt4.7中,如果不加这条语句,m对象的槽函数将无法执行。

在Qt5.10.1中则是可以的。

线程中存在事件循环,信号与槽的机制需要事件循环的支持,QThread类中提供的exec()函数用于开启线程的事件循环,只有事件循环开启后,槽函数才能在信号发送后被调用。


研究槽函数的具体执行线程有什么意义?

当信号的发送与对应槽函数的执行在不同线程中,可能产生临界资源的竞争问题。

将exec()的注释去掉,执行后结果如下:


可以发现,run()函数体中的

qDebug() << "void TestThread::run() -- end tid" << QThread::currentThreadId();

并没有被执行。

如果线程体函数中开启了事件循环,线程应该如何结束?

quit()和exit()函数用于结束事件循环,quit()  <--> exit(0), exec()的返回值有exit()参数决定。

注意:无论事件循环是否开启,信号发送后会直接进入对象所依附的事件队列,然而,只有开启了事件循环,对应的槽函数才会在线程中被调用。

    t.start();

    t.wait(8 * 1000);

    t.quit();  //exit(0);

在主函数中添加上述代码,在等待一定时间(8*1000ms)后,执行quit(),结束事件循环,打印end。

设计相关问题:

什么时候需要在线程中开始事件循环?

事务性操作(间断性IO操作等)可以开启线程的事件循环,每次操作通过发送信号的方式使得槽函数在子线程中执行。

概念——文件缓冲区

默认情况下,文件操作时会开辟一段内存作为缓冲区,向文件中写入的数据回先进入缓冲区,只有当缓冲区满或者遇见换行符的时候,才会将数据写入磁盘。

意义在于,减少磁盘的低级IO操作,提高文件的读写效率。

示例如下:

class FileWriter : public QObject
{
    Q_OBJECT

    class Worker : public QThread
    {
    protected:
        void run();
    };

    QFile m_file;
    Worker m_worker;
public:
    explicit FileWriter(QString file, QObject *parent = 0);
    bool open();
    void write(QString text);
    void close();
    ~FileWriter();
signals:
    void doWrite(QString text);
    void doClose();
protected slots:
    void writeSlot(QString text);
    void closeSlot();
};
#include "FileWriter.h"
#include <QDebug>

void FileWriter::Worker::run()
{
    qDebug() << "void FileWriter::Worker::run() - begin: tid = " << currentThreadId();

    exec();

    qDebug() << "void FileWriter::Worker::run() - end";
}

FileWriter::FileWriter(QString file, QObject *parent) :
    QObject(parent), m_file(file)
{
    connect(this, SIGNAL(doWrite(QString)), this, SLOT(writeSlot(QString)));
    connect(this, SIGNAL(doClose()), this, SLOT(closeSlot()));

    moveToThread(&m_worker);

    m_worker.start();
}

bool FileWriter::open()
{
    return m_file.open(QIODevice::WriteOnly | QIODevice::Text);
}

void FileWriter::write(QString text)
{
    qDebug() << "void FileWriter::write(QString text) tid = " << QThread::currentThreadId();  //主函数发送信号三次  主线程

    emit doWrite(text);
}

void FileWriter::close()
{
    qDebug() << "void FileWriter::close() tid = " << QThread::currentThreadId();

    emit doClose();
}

void FileWriter::writeSlot(QString text)
{
    qDebug() << "void FileWriter::writeSlot(QString text) tid = " << QThread::currentThreadId();  //子线程中调用

    m_file.write(text.toAscii());
    m_file.flush();   //写入磁盘
}

void FileWriter::closeSlot()
{
    qDebug() << "void FileWriter::closeSlot() tid = " << QThread::currentThreadId();

    m_file.close();
}

FileWriter::~FileWriter()
{
    m_worker.quit();
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main() tid = " << QThread::currentThreadId();

    FileWriter writer("L:/Test/test322.txt");

    if( writer.open() )
    {
        writer.write("willwilling\r\n");
        writer.write("1111\r\n");
        writer.write("\n");
        writer.close();
    }

    return a.exec();
}


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值