在项目中,遇到一个问题:两个线程同时执行视觉检测任务,但是先完成的线程要等待后完成的线程的检测结果,两个检测结果综合判定一下,两个线程再同时进行下一轮检测。本文先说解决思路,再放代码。
我使用了QT的条件信号QWaitCondition来完成功能,大概思路是:先完成的线程通过QWaitCondition的wait函数,将线程挂起。而后完成的线程,通过QWaitCondition的wake方法,唤醒所有挂起的函数。
上面的功能涉及到几个公共变量:
(1)flag:刚刚提到先完成的线程和后完成的线程,这个先后如何判断呢? 我通过一个公共变量flag来控制,flag==0的时候,说明还没有线程完成这一轮检测,先完成的线程把flag置成1。后完成的线程发现flag为1,说明已经有一个线程完成了,之后再把flag清0。
(2)res:储存先完成的线程的检测结果。
这个条件变量,我看了很多文档都没有看懂,这里说一说我自己的一个比较通俗的理解,因为我对线程刚刚入门,可能理解有误或者不到位的地方。
比如,有两个线程,分别叫A和B。一个变量不满足某一条件时,线程A挂起。当线程B修改这个变量,B会唤醒挂起的A。意思就是,条件变量能够让B线程控制A线程。
通过以下四个变量来实现 (1)一个锁,QMutex (2)两个条件变量方法,QWaitCondition.wait 和 QWaitCondition.wake。QWaitCondition是QT封装的一个实现了条件变量的类(3)一个挂起的条件
满足挂起条件时,线程A挂起,直到B线程发出信号唤醒它。
#define FINISH 1 //有一个线程先干完活了
#define UN_FINISH 0 // 两个线程都没干完活
QMutex condMuxtex;
QWaitCondition waitAnotherThread;
int flag = UN_FINISH;
condMuxtex.lock(); // 获取锁
if(flag==UN_FINISH){ // 满足挂起条件,线程是先完成任务的
res = result;
flag=FINISH;;
waitAnotherThread.wait(&condMuxtex); //挂起
}
else{ // 线程是后一个完成任务的
flag=UN_FINISH;
int finalres = result&&res;
waitAnotherThread.wakeAll(); // 唤醒另一个线程
}
condMuxtex.unlock(); // 解锁
需要注意的是两个线程的run函数都是一样的。这里放的代码的是两个线程的run函数的部分内容。这部分应该很容易看懂,两个线程都会执行这个run函数,但是我通过flag判断线程是先完成的线程还是后完成的线程。先完成的线程A会执行if语句,后完成的线程B会执行else语句。(flag==未完成)就是上述的挂起条件,满足条件的线程就挂起了,不满足条件的,是后完成的线程,他改变条件,唤醒另一个线程。
这里有一个很灵性的锁condMuxtex。它作为wait的变量传入。线程先获取锁condMuxtex,在线程挂起后,wait函数会释放这个锁,线程被唤醒的时候这个wait函数又会把锁加上,以保证锁的状态没有变。也就是说,代码里看上去只有一次上锁/解锁的过程,实际上,在线程挂起时,锁还被处理了一波。此过程中锁的状态如下:
A先加锁(mutex.lock)-----> A挂起时锁被释放(wait函数释放锁)------>B加锁(mutex.lock)--------> B唤醒A,A等着B释放锁(因为A现在手里没锁)---------》B释放锁(mutex.unlock)-------》A唤醒把锁加上(wait函数加锁)------》A把锁释放(mutex.unlock)
这个锁是为了保护公共变量flag,如果没有这个锁,有可能在线程A修改为FINISH之前,线程B就执行到if语句时,flag还等于UN_FINISH,这时候线程B也进入了if语句,结果导致两个线程都挂起了,从而出现卡死的状态。有了这个锁之后,A先竞争赢了,拿到锁,B会因为得不到锁而等待,卡在lock语句那里。A修改了flag状态之后,把锁放开,B这个时候才有资格拿到锁。这时候,B读到的就是A修改过的flag状态了。
本代码是两个线程均随机等待一段时间,并产生一个随机结果,以便条件变量的测试。
#-------------------------------------------------
#
# Project created by QtCreator 2020-04-14T11:57:01
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = learnThread
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++11
SOURCES += \
main.cpp \
mainwindow.cpp \
workthread.cpp
HEADERS += \
mainwindow.h \
workthread.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#ifndef WORKTHREAD_H
#define WORKTHREAD_H
#include
class WorkThread : public QThread
{
public:
WorkThread();
WorkThread(int n, unsigned int seed); // n 线程ID, seed随机数种子
private:
virtual void run(); // 线程执行的任务
public:
int id;
unsigned int sd;
signals:
public slots:
};
#endif // WORKTHREAD_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include "workthread.h"
#include "QDebug"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
WorkThread *th1 = new WorkThread(1,1234);
WorkThread *th2 = new WorkThread(2,4321);
qDebug()<<"线程测试开始:";
th1->start();//启动该线程1
th2->start(); //启动线程2
}
MainWindow::~MainWindow()
{
delete ui;
}
#include "workthread.h"
#include "QDebug"
#include "QMutex"
#include "QWaitCondition"
#define random(x) (rand()%x) //产生随机数,范围为0~x
#define FINISH 1 //有一个线程先干完活了
#define UN_FINISH 0 // 两个线程都没干完活
int flag = UN_FINISH;
int res = 0;
QMutex condMuxtex;
QWaitCondition waitAnotherThread;
WorkThread::WorkThread()
{
}
WorkThread::WorkThread(int n, unsigned int seed)
{
id = n;
sd = seed;
}
void WorkThread::run()
{
int time = 1000;
while(time--)
{
printf("thread %d, time=%d,", id, time);
// 产生随机睡眠时间
unsigned int sd1 = unsigned(rand());
srand(sd+sd1);
unsigned long t;
t = random(2)+1;
// 睡眠
int result = random(2);
printf("This is thread %d, randomly sleeps for %d seconds,result=%d\n", id, int(t), result);
fflush(stdout);
sleep(t);
// 线程结束
condMuxtex.lock();
if(flag==UN_FINISH){
res = result;
flag=FINISH;
printf("thread %d wait!\n", id);
fflush(stdout);
waitAnotherThread.wait(&condMuxtex);
}
else{
flag=UN_FINISH;
int finalres = result&&res;
printf("thread %d send result %d&&%d!\n", id, res, result);
fflush(stdout);
waitAnotherThread.wakeAll();
}
condMuxtex.unlock();
}
}
可以看到,线程1和线程2是一起开始执行的。由于睡眠时间不同,有时线程1需要等待线程2,有时线程2需要等待线程1。后面完成的那个线程会综合两个线程的结果(0或1)。