QT多线程 第一节

在qt中使用了多线程,有些事项是需要额外注意的:

  • 默认的线程在qt中称之为窗口线程,也叫主线程(UI线程)。负责窗口事件处理或者控件数据的更新。
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理。
  • 主线程和子线程之间如果需要进行数据的传递,需要使用qt的信号槽机制。

1. 线程类QThread 

Qt中提供一个线程类,通过这个类就可以创建子线程了,Qt中一共提供了两种创建子线程的方式。这个类的常用API函数:

 public函数
QThread(QObject *parent = nullptr)构造函数
virtual ~QThread()析构函数
QAbstractEventDispatcher * eventDispatcher() const
bool isFinished() const判断线程中的任务是不是处理完毕了
bool isInterruptionRequested() const
bool isRunning() const判断子线程是不是在执行任务
int loopLevel() const
QThread::Priority priority() const得到当前线程的优先级
void requestInterruption()
void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher)
void setPriority(QThread::Priority priority)设置线程优先级
void setStackSize(uint stackSize)
uint stackSize() const
bool wait(QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever))
bool wait(unsigned long time)等待任务完成,然后退出线程,一般情况会在exit()后边调用这个函数
槽函数
void exit(int returnCode = 0)退出线程,停止底层的事件循环
void quit()和调用exit()效果是一样的,使用这个函数后,再调用wait()函数
void start(QThread::Priority priority = InheritPriority)启动子线程
void terminate()线程退出,可能是会马上终止线程,一般情况下不使用这个函数
信号函数
void finished()线程中执行的任务完成了发出该信号,,任务函数中的处理逻辑执行完毕
void started()开始工作之前发出这个信号,一般不使用
静态公共成员
QThread * create(Function &&f, Args &&... args)
QThread * currentThread()返回一个指向管理当前执行线程的QThread的指针
Qt::HANDLE currentThreadId()
int idealThreadCount()返回可以在系统上运行的理想线程数 == 当前电脑的CPU核心数相同
void msleep(unsigned long msecs)线程休眠函数 毫秒
void sleep(unsigned long secs)线程休眠函数 秒
void usleep(unsigned long usecs)线程休眠函数 微秒
void yieldCurrentThread()
受保护的函数
int exec()
virtual void run()子线程要处理什么任务,需要写到run()中

2. 使用方式 1

2.1 操作步骤

QT中提供的多线程的第一种使用方式的特点是:简单。操作步骤如下:1. 需要创建一个线程的子类,让其继承QT中的线程类QThread,比如:

1.  创建一个类对象

class MyThread : public QThread
{
    ...
}

2. 重写父类run()方法,在该函数内部编写子线程要处理的具体的业务流程

class MyThread : public QThread
{
    ... ...
protected:
    void run()
    {
        ... ...
    }

}

3. 在主线程中创建子线程对象,new 一个就可以了

MyThread* subThread = new MyThread;

4. 启动子线程,调用start()方法

subThread->start();

不能在类的外部调用run()方法启动子线程,在外部调用start()相当于让run()开始运行,当子线程创建出来后,父子线程之间的通信可以通过信号槽的方式,注意事项:

  • 在Qt中在子线程中不要操作程序中的窗口类型的对象,不允许,如果操作的了程序就挂了
  • 只有主线程才能操作程序中的窗口对象,默认的线程就是主线程,自己创建的就是子线程

3. 使用方式 2

3.1 操作步骤

Qt提供的第二种线程的创建方式弥补了第一种的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,其具体操作步骤如下:

1.创建一个新的类,让这个类从QObject派生

class MyWork : public QObject
{
    ... ...
}

2. 这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑

class MyWork : public QObject
{
public:
    ... ...

    //函数名自己指定,叫什么都可以,参数可以根据实际需求添加
    void working();

}

3. 在主线程中创建一个QThread对象,这就是子线程的对象

QThread* sub = new QThread;

4. 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)

MyWork* work = new MyWork;

5. 将MyWork 对象移动到创建的子线程对象中,需要调用QObject类提供的moveToThread()方法

//移动到子线程中工作
work->moveToThread(sub);

6. 启动子线程,调用start(),这时候线程就启动了,但是移动到线程中的对象并没有工作

7. 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

部分代码:MyThread类的代码

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

//生产随机数
class Generate  : public QThread
{
	Q_OBJECT

public:
	Generate(QObject *parent);
	~Generate();
public slots:
	void RecvNum(int num);

protected:
	void run() override;
signals:
	void sendArray(QVector<int> num);

private:
	int m_num;
};

class BubbleSort : public QThread
{
	Q_OBJECT

public:
	BubbleSort(QObject* parent);
	~BubbleSort();
public slots:
	void RecvArray(QVector<int> list);

protected:
	void run() override;
signals:
	void finish(QVector<int> num);

private:
	QVector<int> m_list;
};

class QuickSort : public QThread
{
	Q_OBJECT

public:
	QuickSort(QObject* parent);
	~QuickSort();
public slots:
	void RecvArray(QVector<int> list);

protected:
	void run() override;
signals:
	void finish(QVector<int> num);

private:
	QVector<int> m_list;

	void quickSort(QVector<int>& list, int l, int r);
};
#endif
#include "MyThread.h"
#include <QElapsedTimer>
#include <QDebug>

Generate::Generate(QObject *parent)	: QThread(parent)
{}

Generate::~Generate()
{}

void Generate::RecvNum(int num)
{
	m_num = num;
}

void Generate::run()
{
	QElapsedTimer time;
	time.start();
	qDebug() << "生成随机数的线程的线程地址 " << QThread::currentThread();
	QVector<int> list;
	for (int i = 0; i < m_num; ++i)
	{
		list.push_back(qrand() % 100000);	
	}
	int milsec = time.elapsed();
	qDebug() << "生成 " << m_num << " 个随机数公用时: " << milsec << "毫秒";
	emit sendArray(list);
}

BubbleSort::BubbleSort(QObject* parent)
{
}

BubbleSort::~BubbleSort()
{
}
void BubbleSort::RecvArray(QVector<int> list)
{
	m_list = list;
}
void BubbleSort::run()
{
	QElapsedTimer time;
	time.start();
	qDebug() << "冒泡排序的线程的线程地址 " << QThread::currentThread();
	for (int i = 0; i < m_list.size(); ++i)
	{
		for (int j = 0; j < m_list.size() - i - 1; ++j)
		{
			if (m_list[j] > m_list[j + 1])
			{
				auto temp = m_list[j];
				m_list[j] = m_list[j + 1];
				m_list[j + 1] = temp;
			}
		}		
	}
	int milsec = time.elapsed();
	qDebug() << " 冒泡排序用时: " << milsec << "毫秒";
	emit finish(m_list);
}

QuickSort::QuickSort(QObject* parent)
{
}

QuickSort::~QuickSort()
{
}

void QuickSort::RecvArray(QVector<int> list)
{
	m_list = list;
}

void QuickSort::run()
{
	QElapsedTimer time;
	time.start();
	qDebug() << "快速排序的线程的线程地址 " << QThread::currentThread();
	quickSort(m_list,0,m_list.size() -1);
	int milsec = time.elapsed();
	qDebug() << " 快速排序用时: " << milsec << "毫秒";
	emit finish(m_list);

}

void QuickSort::quickSort(QVector<int>& list, int l, int r)
{
	if (l < r)
	{
		int i = l, j = r;
		//拿出第一个元素,保存到x中, 第一个位置成为一个坑
		int x = list[l];
		while (i < j)
		{
			//从右向左找最小的x的数
			while (i < j && list[j] >= x)
			{
				//左移,直到遇到小于等于x的数
				j--;			
			}
			if (i < j)
			{
				//将右侧找到的小于x的元素放入左侧的坑中,右侧出现一个坑,左侧元素索引后移
				list[i++] = list[j];
			}
			while (i < j && list[i] < x)
			{
				//右移,知道遇到大于x的数
				i++;
			}
			if ( i < j)
			{
				//将左侧找到的元素放到右侧坑中,左侧出现一个坑,右侧元素索引向前移动
				list[j--] = list[i];
			}
		}
		//此时 i = j,将保存在x中的数填入坑中
		list[i] = x;
		quickSort(list,l,i - 1);
		quickSort(list,i + 1, r);
	}
}


第二种 开线程的代码:

			workEEGThread->moveToThread(_GPUThread.get());
			emit RunGPUThreadWorking();
			_GPUThread->start();

这个就比较简单了,思路就是,设置一个工作类,然后再设置一个工作线程,这里工作类是workEEGThread 工作线程是GPUThread,由于workEEGThread继承与OQbject所以里面有个通用的方法,moveToThread,就是把工作类移动到线程中,所以这个workEEGThread->moveToThread(_GPUThread.get());是把workEEGThread线程移动到_GPUThread线程中。

但是需要注意一点就是需要一个信号槽函数,告诉线程你要启动那个函数。

void EEGData::Registration()
{
	connect(this, &EEGData::RunGPUThreadWorking, workEEGThread.get(), &WorkEEGThread::working);

}

这就是emit RunGPUThreadWorking();这行代码的原因。然后_GPUThread->start();运行这行代码就可以启动线程了。线程函数就进入了workEEGThread类的working函数中,working是由信号槽函数指定的。之前没有指定信号槽函数,怎么调试都进不去新线程中,所以这个记录一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

波雅_汉库克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值