【代码】线程同步条件变量的简单应用:让一个线程等待另一个线程

一. 问题描述

在项目中,遇到一个问题:两个线程同时执行视觉检测任务,但是先完成的线程要等待后完成的线程的检测结果,两个检测结果综合判定一下,两个线程再同时进行下一轮检测。本文先说解决思路,再放代码。

我使用了QT的条件信号QWaitCondition来完成功能,大概思路是:先完成的线程通过QWaitCondition的wait函数,将线程挂起。而后完成的线程,通过QWaitCondition的wake方法,唤醒所有挂起的函数。

二、其他问题

上面的功能涉及到几个公共变量:

(1)flag:刚刚提到先完成的线程和后完成的线程,这个先后如何判断呢? 我通过一个公共变量flag来控制,flag==0的时候,说明还没有线程完成这一轮检测,先完成的线程把flag置成1。后完成的线程发现flag为1,说明已经有一个线程完成了,之后再把flag清0。

(2)res:储存先完成的线程的检测结果。

三. 条件变量

这个条件变量,我看了很多文档都没有看懂,这里说一说我自己的一个比较通俗的理解,因为我对线程刚刚入门,可能理解有误或者不到位的地方。

1. 条件变量的作用

比如,有两个线程,分别叫A和B。一个变量不满足某一条件时,线程A挂起。当线程B修改这个变量,B会唤醒挂起的A。意思就是,条件变量能够让B线程控制A线程。

2. 条件变量的实现

通过以下四个变量来实现 (1)一个锁,QMutex (2)两个条件变量方法,QWaitCondition.wait  和 QWaitCondition.wake。QWaitCondition是QT封装的一个实现了条件变量的类(3)一个挂起的条件

满足挂起条件时,线程A挂起,直到B线程发出信号唤醒它。

3. 关键代码

#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状态了。

四、完整代码

本代码是两个线程均随机等待一段时间,并产生一个随机结果,以便条件变量的测试。

(1)代码结构

【代码】线程同步条件变量的简单应用:让一个线程等待另一个线程_第1张图片

(2)代码

learnThread.pro

#-------------------------------------------------
#
# 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

MainWindow.h

#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

workthread.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

mainWindow.cpp

#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;
}

workthread.cpp

#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();
    }
}

(3)运行结果

【代码】线程同步条件变量的简单应用:让一个线程等待另一个线程_第2张图片

可以看到,线程1和线程2是一起开始执行的。由于睡眠时间不同,有时线程1需要等待线程2,有时线程2需要等待线程1。后面完成的那个线程会综合两个线程的结果(0或1)。 

你可能感兴趣的:(c++)