qt4.7 之前的线程操作
代码
需要重写一个类,继承QTread
class mythread:public QThread
{
Q_OBJECT //必须加,否则出现一些奇怪问题
public:
mytherad();
protected:
void run();
signals:
sigDone();
}
void run()
{ QThread::sleep(5);
emit sigdone();
}
如何启动子线程?
主线程中加入这个子线程的头文件,然后定义一个mythread对象 myt;
通过一个函数 myt.start(); 就会启动。当子线程完成操作的时候,可以向父进程发出一个信号,主线程里收到这个信号交给某个函数处理
也就是connect把两个连接起来
这里有个堵塞的方法
如果mythread有个变量,让子进程去修改这个变量,主进程里while一直判断这个变量是否被修改,当子进程被修改了,主进程继续向下进行
总结:某个类继承QThread,在这个类里重写run方法,run方法里是想让子线程想要执行的内容
在主线程里面新建这个类的一个对象,然后对象.start() 将会执行
然后一些其他的就是执行完了可以传递信号回到主进程,让主进程里的某个槽函数进行后续的处理
qt4.7 之后的线程操作
特点:灵活的指定一个线程对象的线程入口函数
多线程使用注意事项
1.业务对象,构造的时候不能指定父对象
2.子线程中不能处理ui窗口(ui相关的类)
3.子线程中只能处理一些数据相关的操作,不能涉及窗口
主线程:
OtherTread类包含:
信号:结束信号
槽函数:子线程进程进行工作的函数
主线程包含:
子线程的头文件
信号:发出子线程开始工作的函数
槽函数:接收子线程完成工作的函数
QThread *qt;
函数 发送要子线程开始工作的函数
app里面:
OtherTread *otherqt =new OtherTread();
qt=new QThread ();
otherqt->moveToThread(qt)
qt->start();
connnect(this,SIGNAL(让子线程开始的函数),otherqt ,SLOT(子线程工作的函数))
connect(子线程完成与当前的某个槽函数连接)
总结:
和上面一样,新建一个想要执行逻辑的类的对象,再新建一个QThread对象 第一个对象->moveToThread(进程对象) 然后在父进程里面就控制了这个新写的类对象的进程,让QThread对象启动,这时的启动并没有真正的启动,需要用信号槽机制发送信号以及绑定需要执行的函数
这样做的目的是可以将任意的对象选择性的交给任意的进程对象。
子线程的关闭
一般:
可以用信号槽的方式将窗口析构与子进程销毁连接上
connect(this, &class的名::destroyed,this,&class的名::一个槽函数)
这个槽函数里面
{
子线程对象.quit()
子线程对象.wait() //等待线程的工作完成
}
需要注意的是,前提是子线程的工作已经运行完
而且这个子线程是新建的线程对象,并非某个class的对象
下面代码的问题: 局部对象t在start后就会被销毁,同时成员变量 i 也会被销毁,然而线程还在运行,非法访问已经被销毁的变量
同步型线程设计
在析构函数中先调用wait()函数,强制等到线程运行结束
这种设计适合 线程生命期相对较短的情形
SyncThread::~SyncThread()
{
wait(); // 等待线程结束才析构
qDebug() << "SyncThread::~SyncThread() destroy thread object";
}
异步型线程设计
在 run() 中最后调用 deleteLater() 函数 ,线程体函数主动申请销毁线程对象
这种设计适合线程生命期不可控,需要长时间运行于后台情形
void AsyncThread::run()
{
qDebug() << "void AsyncThread::run() tid = " << currentThreadId();
for(int i=0; i<3; i++)
{
qDebug() << "void AsyncThread::run() i = " << i;
sleep(1);
}
qDebug() << "void AsyncThread::run() end";
deleteLater(); // 通知销毁当前线程对象 void QObject::deleteLater () [slot]
}
进程同步
比如有个加法运算(加法运算类里有三个值,前两个值是要运算的数,第三个值是他们加后的结果,第三个值默认为0)
在主进程里新建三个子进程让他们执行各自的加法运算
最后在主进程里面将这三个对象的第三个值也就是结果再加起来
在这里重点说明一下:只要一个类继承了QThread 而且还重写了run方法 如果在某个地方有三个这个类的对象,那么在调用各个对象的start后,那么将会是用子进程进行处理的,不再是顺序的调用了,哪个进程先执行完无法获悉
如果想要同步,就需要用的wait(0
这里注意wait和sleep的区别
sleep()方法的作用是 让当前正在执行的线程休眠指定的毫秒数
wait()方法
这个图片是说在哪里对某个对象调用wait,哪里会等待那个对象执行完毕
比如上面有个对象a执行了加法运算,那么再调用a.wait()便是等待a完成以后再向下进行,因此说,上面说的那个堵塞的方法是极其愚蠢的
线程锁
QMutex中的关键成员函数
void lock()
当锁空闲时,获取锁并执行
当锁被获取,则自身会堵塞并等待锁被释放
void unlock()
释放锁
需要注意的是:同一把锁的加锁和释放锁必须在同一线程中出现
伪代码例子
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QMutex>
#include <QDebug>
static QMutex g_mutex; // 线程锁
static QString g_store; // 仓库
class Producer : public QThread
{
protected:
void run()
{ int count = 0;
while(true)
{
g_mutex.lock();
g_store.append(QString::number((count++) % 10));
qDebug() << objectName() << " : " + g_store;
g_mutex.unlock();
msleep(1);
}
}
};
class Customer : public QThread
{
protected:
void run()
{
while( true )
{
g_mutex.lock();
if( g_store != "" )
{
g_store.remove(0, 1);
qDebug() << objectName() << " : " + g_store;
}
g_mutex.unlock();
msleep(1);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer p;
Customer c;
p.setObjectName("Producer");
c.setObjectName("Customer");
p.start();
c.start();
return a.exec();
}
上述可以看出要先有一把锁mutex,然后对于临界资源加这把锁,对于临界资源加锁并非临界资源有特定的方法,换种说法是临界资源感知不到锁的存在,只是逻辑上加上锁了,此外,每一个临界资源都有特定的一把锁,不能把一个mutex逻辑上给多个临界资源加锁
且需要注意的是,如果用while判断,每次在while结尾需要加上让自身睡上几秒,不要让while一直运行
信号量
操作系统学习的时候,对于信号量和锁会有下面这种体会
伪代码
QSemaphore g_sem_free(5); // 5个可生产资源
QSemaphore g_sem_used(0); // 0个可消费资源
生产者
g_sem_free.acquire(); g_sem_free信号量减一 acquire获取不到会在这里堵塞
… 主要逻辑段
g_sem_used.release(); g_sem_used信号量 加一
消费者
g_sem_used.acquire();
… 主要逻辑段
g_sem_free.release();
#include <QtCore/QCoreApplication>
#include <QThread>
#include <QSemaphore>
#include <Qdebug>
const int SIZE = 5;
unsigned char g_buff[SIZE] = {0};
QSemaphore g_sem_free(SIZE); // 5个可生产资源
QSemaphore g_sem_used(0); // 0个可消费资源
// 生产者生产产品
class Producer : public QThread
{
protected:
void run()
{
while( true )
{
int value = qrand() % 256;
// 若无法获得可生产资源,阻塞在这里
g_sem_free.acquire();
for(int i=0; i<SIZE; i++)
{
if( !g_buff[i] )
{
g_buff[i] = value;
qDebug() << objectName() << " generate: {" << i << ", " << value << "}";
break;
}
}
// 可消费资源数+1
g_sem_used.release();
sleep(2);
}
}
};
// 消费者消费产品
class Customer : public QThread
{
protected:
void run()
{
while( true )
{
// 若无法获得可消费资源,阻塞在这里
g_sem_used.acquire();
for(int i=0; i<SIZE; i++)
{
if( g_buff[i] )
{
int value = g_buff[i];
g_buff[i] = 0;
qDebug() << objectName() << " consume: {" << i << ", " << value << "}";
break;
}
}
// 可生产资源数+1
g_sem_free.release();
sleep(1);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Producer p1;
Producer p2;
Producer p3;
p1.setObjectName("p1");
p2.setObjectName("p2");
p3.setObjectName("p3");
Customer c1;
Customer c2;
c1.setObjectName("c1");
c2.setObjectName("c2");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
return a.exec();
}
子线程与主线程通讯的方法
主要思想就是子线程发送信号,父进程提前将信号和自己的某个槽函数绑定