QT中的多线程-与主线程通信

简介:

今天回想研究生期间做的项目,用到了Qt的多线程通信,当时一点都不懂,就这照猫画虎地写,如今因为上次面试中问到了,觉得得好好准备下:

Qt 程序开始执行时,唯一的一个线程 —— 主线程 (main thread)也开始执行。主线程是唯一的,因为只有它才能创建 QApplication 或者是 QCoreApplication 对象,只有它才能通过应用程序对象调用 exec( ) 函数,只有它才能在 exec( ) 执行完毕后等待并处理事件。

主线程可以通过创建 QThread 子类对象开启一个新的线程,如果这些线程间需要相互通讯,它们可以使用共享变量,同时使用 mutexes,read-write locks,semaphores 或者 wait conditions 一些方法保持共享变量访问的同步性。但是由于这些技术可能锁定 event loop,同时还会冻结用户界面,所以其中没有一个能完成与主线程之间的通讯。

完成第二线程(secondary thread)与主线程之间的通讯的方法是:跨线程间的 signal-slot 连接。signal 一旦发出,其对应的 slot 函数便立即执行,这种连接是一种同步的机制。

但是当我们将不同线程中的对象连接在一起时,这种 signal-slot 通讯机制变得“不同步”(asynchronous)。signal-slot 机制的底层实现是传递一个 event,然后 slot 由 receiver 对象所在的线程中的 event loop 调用。默认情况下,一个 QObject 对象存在于创建它的线程中,但是任何时刻,调用 QObject : : moveToThread( ) 函数可以改变这种关系。


当时我们的程序中其实有两个大类,一个是Transaction类,一个是Transaction Thread类,可以看到Transaction是个抽象类,里面的纯虚函数Execute虽然在子类的实现中都是空的,但是目的就是为了让Transaction成为一个抽象类,

Transaction类:

class Transaction {
public:
	virtual ~Transaction() {
	}
	virtual void Execute() = 0;
	int getTransactID();
protected:
	int transactID;
};

class LoginTransaction: public Transaction {
public:
	LoginTransaction();
	void Execute();
private:
};

在cpp文件中对LoginTransaction的构造函数和必须实现的接口做定义(虽然是空的。。。)

int Transaction::getTransactID() {
	return transactID;
}

LoginTransaction::LoginTransaction() {
	transactID = TR_LOGIN_SUCCESS;
}

void LoginTransaction::Execute() {
}


然后在Transaction Tread类中,相当于第二个线程,通过single-slot向主线程发送信号。


TransactionTread.h文件


class TransactionThread: public QThread {
Q_OBJECT
public:
	TransactionThread();
	virtual ~TransactionThread();
	void addTransaction(Transaction *tr);
protected:
	void run();
signals:
	void loginSuccess();
private:
	QQueue<Transaction*> transactQueue;
	QWaitCondition transactAdded;
	QMutex mutex;
private:
	void ProcessTransact(Transaction* tr);
在这个类中,run 函数在自己的线程中执行,其它的函数则从主线程调用。在这个类中,维护着一个 transaction 队列,其中的每个 transaction 将一个接一个地被执行。
TransactionTread.cpp文件

Transaction * const EndTransaction = 0;

TransactionThread::TransactionThread() {
	start();
}

TransactionThread::~TransactionThread() {
	{
		QMutexLocker locker(&mutex);
		while (!transactQueue.isEmpty())
			delete transactQueue.dequeue();
		transactQueue.enqueue(EndTransaction);
		transactAdded.wakeOne();
	}
	wait();
}

void TransactionThread::addTransaction(Transaction *tr) {
	QMutexLocker locker(&mutex);
	transactQueue.enqueue(tr);
	transactAdded.wakeOne();
}

void TransactionThread::run() {
	Transaction *tr = 0;
	forever {
		{
			QMutexLocker locker(&mutex);
			if (transactQueue.isEmpty())
				transactAdded.wait(&mutex);
			tr = transactQueue.dequeue();
			if (tr == EndTransaction)
				break;
		}
		ProcessTransact(tr);
//		
//		if (tr->getTransactID() != TR_ADDED || )
//			delete tr;
	}
}

void TransactionThread::ProcessTransact(Transaction *tr) {
	switch (tr->getTransactID()) {
	case TR_LOGIN_SUCCESS:
		ProcessTrLogin(tr);
		break;
	case TR_LOGIN_FAIL:
		ProcessTrLoginFail(tr);
		break;
	case TR_ADDED:
		ForwardTransact(tr);
		//ProcessTrAdded(tr);
		break;
	case TR_ADDNORMAL:
		//ForwardTransact(tr);
		ProcessTrAddNormal(tr);
		break;
	case TR_STATUS:
		ProcessTrStatus(tr);
		break;
	case TR_VIEWCONTACT:
		ProcessTrViewContact(tr);
		break;
	case TR_NEW_CONTACT:
		ProcessTrNewContact(tr);
		break;
	case TR_DELETECONTACT:
		ForwardTransact(tr);
		break;
	case TR_BEDELETED:
		ForwardTransact(tr);
		break;
	case TR_NEW_IM:
		ProcessTrNewIM(tr);
		break;
	case TR_INS_CONTACT:
		ProcessTrInsContact(tr);
		break;
	case TR_PHONE_LOST:
		ProcessTrPhoneLost(tr);
		break;
	default:
		break;
	}
}

void TransactionThread::ProcessTrLoginFail(Transaction *tr) {
	tr->Execute();
	emit loginFailure();
}

void TransactionThread::ProcessTrLogin(Transaction *tr) {
	tr->Execute();
	emit loginSuccess();
}

调用 QThread : : start( ) 开启将要执行 transaction 的线程。在析构函数中,清空队列,将一个特殊的 EndTransaction 加入队列。唤醒线程,并使用 QThread : : wait( ) 等待线程结束。如果没有 wait( ),当其它的线程访问类中的成员变量时,程序有可能崩溃。 在析构函数中,QMutexLocker 的析构造函数将被调用。其中将 mutex 解锁,在 wait( ) 之前解锁这很重要,否则将引起死锁的可能性(第二线程一直等待 mutex 被解锁,而主线程一直等待第二线程完成而操持 mutex 不放)。

QWaitCondition : : wakeOne( ) 函数唤醒一个正在等待某个条件的线程。被唤醒的线程取决于操作系统的排程策略,并不能控制和提前预知哪个线程将被唤醒。如果需要唤醒某个指定的线程,通常需要使用不同的等待条件,使用不同的线程专门等待不同的等待条件。

addTransaction( ) 函数将一个 transaction 添加到队列中,并唤醒 transaction 线程。所有访问 transactions 的成员变量都由一个 mutex 保护,因为在第二线程遍历队列中的 transaction 时主线程可能修改这些变量。

run函数中定义了不同的执行方法,获取transaction的id,然后进行不同的侗族,也就是发射出不同的信号。

然后在主线程中,connect这些信号到主线程的槽函数中:

UIControl::UIControl(Manager *p, QApplication* a) {
	app = a;
	manager = p;
	loginpage = NULL;
	waitpage = NULL;
	main = NULL;
	msgbox = new MessageBox(this);
	tr_queue = new TransactionThread();
	connect(tr_queue, SIGNAL(loginSuccess()), this, SLOT(ShowMainFrame()));
没写完,UIControl就是个主界面,通过tr_queue中的不同信号,来调用不同的槽函数。

目录
相关文章
|
3月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
57 3
|
4月前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
55 1
[Java]线程生命周期与线程通信
|
4月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
44 1
|
4月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
83 1
|
4月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
70 1
|
4月前
多线程通信和同步的方式有哪些?
【10月更文挑战第6天】
197 0
|
4月前
|
Java
|
5月前
|
C语言 C++ Windows
QT多插件通信框架CTK编译记录
本文记录了编译QT多插件通信框架CTK的过程,包括编译结果截图、部署配置、Log4Qt编译配置、参考链接和拓展资料。文中提供了详细的编译步骤和配置文件示例,以及相关的资源链接。
140 0
QT多插件通信框架CTK编译记录
|
5月前
|
数据库 数据库管理
qt对sqlite数据库多线程的操作
本文总结了在Qt中进行SQLite数据库多线程操作时应注意的四个关键问题,包括数据库驱动加载、加锁、数据库的打开与关闭,以及QsqlQuery变量的使用。
313 1
|
5月前
自己动手写QT多线程demo
本文是作者关于如何编写Qt多线程demo的教程,介绍了如何实现多线程功能,包括可暂停和继续的功能。文章提供了部分示例代码,展示了如何创建线程类、启动和管理线程,以及线程间的通信。同时,还提供了相关参考资料和免费下载链接。
135 0