QT多线程(二):基于互斥锁与读写锁的线程同步

news2024/12/19 23:42:57

此处需要说明的是,这里的线程同步概念与操作系统中的线程同步并无区别,都是避免多个线程同时访问临界区数据可能产生的读写错误问题。在 Qt 中,有多个类可以实现线程同步的功能,这些类包括 QMutex、QMutexLocker、 QReadWriteLock、QReadLocker、QWriteLocker、QWaitCondition、QSemaphore 等。

线程同步概念

在多线程程序中,由于存在多个线程,线程之间可能需要访问同一个变量,或一个线程需要等待另一个线程完成某个操作后才产生相应的动作。例如在上一节中提到的例程,工作线程生成随机的骰子点数,主线程读取骰子点数并显示,主线程需要等待工作线程生成一新的骰子点数后再读取数据。但上一节中并没有使用线程同步机制,而是使用了信号与槽的机制,在生成新的点数之后通过

信号通知主线程读取新的数据。这个过程类似于操作系统的线程信号机制,都是为信号设定一个处理函数,本章中不使用信号与槽函数来讲解其他方式的线程同步方法。

基于互斥量的线程同步

QMutex 和 QMutexLocker 是基于互斥量(mutex)的线程同步类。

QMutex类

该类提供的API函数如下:

void QMutex::lock() //锁定互斥量,一直等待
void QMutex::unlock() //解锁互斥量
bool QMutex::tryLock() //尝试锁定互斥量,不等待
bool QMutex::tryLock(int timeout) //尝试锁定互斥量,最多等待 timeout 毫秒

函数 lock()锁定互斥量,如果另一个线 程锁定了这个互斥量,它将被阻塞运行直到 其他线程解锁这个互斥量。函数 unlock()解锁互斥量,需要与 lock()配对使用。

函数 tryLock()尝试锁定一个互斥量,如果成功锁定就返回 true,如果其他线程已经锁定了这个互斥量就返回 false。函数 tryLock(int timeout)尝试锁定一个互斥量,如果这个互斥量被其他线程锁定,最多等待 timeout 毫秒。

互斥量相当于一把钥匙,如果两个线程要访问同一个共享资源,就需要通过 lock()或 tryLock()拿到这把钥匙,然后才可以访问该共享资源,访问完之后还要通过unlock()还回钥匙,这样别的线程才有机会拿到钥匙。

在上一节的例程中,在TDiceThread类中添加一个QMutex变量,并删除自定义信号newValue(),增加一个readValue()函数用于提供给主窗口访问类变量。

QMutex  mutex;  //互斥量


bool TDiceThread::readValue(int *seq, int *diceValue)
{
    if (mutex.tryLock(100))  //尝试锁定互斥量,等待100ms
    {
        *seq=m_seq;
        *diceValue=m_diceValue;
        mutex.unlock();     //解锁互斥量
        return true;
    }
    else
        return false;
}

主函数(主线程)在访问m_seq和m_diceValue变量时,会尝试获取“钥匙”,最多等待100ms,得到权限后会通过指针类变量返回值。事后解锁以便于工作线程对这两个变量进行修改。

另一处需修改的是工作线程中的访问,代码如下:

void TDiceThread::run()
{//线程的事件循环
    m_stop=false;       //启动线程时令m_stop=false
    m_paused=true;      //启动运行后暂时不掷骰子
    m_seq=0;            //掷骰子次数
    while(!m_stop)      //循环主体
    {
        if (!m_paused)
        {
            mutex.lock();       //锁定互斥量
            m_diceValue=0;
            for(int i=0; i<5; i++)
                m_diceValue += QRandomGenerator::global()->bounded(1,7);  //产生随机数[1,6]
            m_diceValue =m_diceValue/5;
            m_seq++;
            mutex.unlock();     //解锁互斥量
        }
        msleep(500);    //线程休眠500ms
    }
    quit();     //在  m_stop==true时结束线程任务
}

在函数 run()中,我们对重新计算变量 m_diceValue 和 m_seq 值的代码片段用互斥量 mutex 进行了保护。工作线程运行后,其内部的函数 run()一直在运行。主线程里调用工作线程的 readValue()函数,其实际是在主线程里运行的。

通过上述方式,主线程和工作线程都对临界区的变量进行了互斥访问,这样就可确保数据的完整性。

QMutexLocker 类

QMutexLocker 是另一个简化了互斥量处理的类。QMutexLocker 的构造函数接受互斥量作为参数并将其锁定,QMutexLocker 的析构函数则将此互斥量解锁,所以在 QMutexLocker 实例变量的生存期内的代码片段会得到保护,自动进行互斥量的锁定和解锁。

void TDiceThread::run()
{//线程的事件循环
    m_stop=false;       //启动线程时令m_stop=false
    m_paused=true;      //启动运行后暂时不掷骰子
    m_seq=0;            //掷骰子次数
    while(!m_stop)      //循环主体
    {
        if (!m_paused)
        {
            QMutexLocker locker(&mutex);
            m_diceValue=0;
            for(int i=0; i<5; i++)
                m_diceValue += QRandomGenerator::global()->bounded(1,7);  //产生随机数[1,6]
            m_diceValue =m_diceValue/5;
            m_seq++;
        }
        msleep(500);    //线程休眠500ms
    }
    quit();     //在  m_stop==true时结束线程任务
}

这两种实现互斥访问的功能一样,使用是分别注意其形式即可。

在主窗口类的构造函数中,设置了一个定时器,每隔一段时间读取一次临界区变量,如果成功获取到锁并且数据也是最新的,则据此更新主界面。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    threadA= new TDiceThread(this);
    connect(threadA,&TDiceThread::started, this, &MainWindow::do_threadA_started);
    connect(threadA,&TDiceThread::finished,this, &MainWindow::do_threadA_finished);

    timer= new QTimer(this);     //创建定时器
    timer->setInterval(200);
    timer->stop();
    connect(timer,&QTimer::timeout, this, &MainWindow::do_timeOut);
}


void MainWindow::do_timeOut()
{
    int tmpSeq=0,tmpValue=0;
    bool  valid=threadA->readValue(&tmpSeq,&tmpValue); //读取数值
    if (valid && (tmpSeq != m_seq)) //有效,并且是新数据
    {
        m_seq=tmpSeq;
        m_diceValue=tmpValue;
        QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",m_seq,m_diceValue);
        ui->plainTextEdit->appendPlainText(str);
        QString filename=QString::asprintf(":/dice/images/d%d.jpg",m_diceValue);
        QPixmap pic(filename);
        ui->labPic->setPixmap(pic);
    }

}

在开始和结束按钮槽函数中,需要设置定时器的启动和停止。代码如下:

void MainWindow::on_actDice_Run_triggered()
{//"开始"按钮,开始掷骰子
    threadA->diceBegin();
    timer->start();     //重启定时器
    ui->actDice_Run->setEnabled(false);
    ui->actDice_Pause->setEnabled(true);
}

void MainWindow::on_actDice_Pause_triggered()
{//"暂停"按钮,暂停掷骰子
    threadA->dicePause();
    timer->stop();      //停止定时器
    ui->actDice_Run->setEnabled(true);
    ui->actDice_Pause->setEnabled(false);
}

基于读写锁的线程同步

使用互斥量时存在一个问题,即每次只能有一个线程获得互斥量的使用权限。如果在一个程序中有多个线程读取某个变量,使用互斥量时必须排队。而实际上若只是读取一个变量,可以让多个线程同时访问,这种情况下使用互斥量就会降低程序的性能。

因此提出了读写锁概念,Qt 提供了读写锁类 QReadWriteLock,它是基于读或写的方式进行代码片段锁定的,在多个线程读写一个共享数据时,使用它可以解决使用互斥量存在的上面所提到的问题。

QReadWriteLock 以 读或写锁定的同步方法允许以读或写的方式保护一段代码,它可以允许多个线程以只读方式同步访问资源,但是只要有一个线程在以写入方式访问资源,其他线程就必须等待,直到写操作结束。

简单总结就是:同一时间,多个线程可以同时读,只有一个线程可以写,读写不能同时进行。

QReadWriteLock类 提供以下几个主要的函数:

void lockForRead() //以只读方式锁定资源,如果有其他线程以写入方式锁定资源,这个函数会被阻塞
void lockForWrite() //以写入方式锁定资源,如果其他线程以读或写方式锁定资源,这个函数会被阻塞
void unlock() //解锁
bool tryLockForRead() //尝试以只读方式锁定资源,不等待
bool tryLockForRead(int timeout) //尝试以只读方式锁定资源,最多等待 timeout 毫秒
bool tryLockForWrite() //尝试以写入方式锁定资源,不等待
bool tryLockForWrite(int timeout) //尝试以写入方式锁定资源,最多等待 timeout 毫秒

例如下列案例:

int buffer[100]; 
QReadWriteLock Lock; //定义读写锁变量
void ThreadDAQ::run() //负责采集数据的线程
{ ... 
 Lock.lockForWrite(); //以写入方式锁定
 get_data_and_write_in_buffer(); //数据写入 buffer 
 Lock.unlock();
  ... 
} 
void ThreadShow::run() //负责显示数据的线程
{ ... 
 Lock.lockForRead(); //以读取方式锁定
 show_buffer(); //读取 buffer 里的数据并显示
 Lock.unlock(); 
 ... 
} 
void ThreadSaveFile::run() //负责保存数据的线程
{ ... 
 Lock.lockForRead(); //以读取方式锁定
 save_buffer_toFile(); //读取 buffer 里的数据并保存到文件
 Lock.unlock(); 
 ... 
}

另外,QReadLocker 和 QWriteLocker 是 QReadWriteLock 的简便形式,如同 QMutexLocker 是 QMutex 的简便形式一样,无须与 unlock()配对使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ChatGPT】解锁AI思维链:如何让机器像人类一样思考?

在人工智能领域&#xff0c;我们一直在追求让机器像人类一样思考。然而&#xff0c;即使是最先进的AI&#xff0c;也常常被诟病缺乏“常识”&#xff0c;难以理解复杂问题&#xff0c;更不用说像人类一样进行逻辑推理和解决问题了。最经常的表现就是遇到不会的地方&#xff0c;…

重新定义页签!Choerodon UI Tabs让管理更高效

01 引言 Tabs 组件通过提供平级区域&#xff0c;将大块内容进行有效的收纳和展现&#xff0c;从而保持界面整洁。但在企业应用的快速发展中&#xff0c;这样传统的页签组件已无法满足我们对界面布局和个性化展示的追求。Choerodon UI Tabs 组件通过支持多级分组、个性化配置、…

机器学习之偏差

机器学习中的偏差&#xff08;Bias&#xff09;是指模型的预测值与真实值之间的系统性误差&#xff0c;或者说模型无法准确捕捉数据中复杂模式的能力。偏差通常与模型的假设或学习能力有关&#xff0c;过高的偏差会导致模型的性能不佳&#xff0c;表现为欠拟合。 偏差的来源 模…

SSH连接监控以及新用户创建和系统资源访问限制

目录 监控连接数SSH连接数的限制和影响理论限制可能的影响 创建SSH新用户为每个ssh用户配置系统资源限制1. 使用 /etc/security/limits.conf 限制资源2. 使用 cgroups 控制资源3. 磁盘配额限制4. 限制 SSH 访问5. 使用 PAM 限制6. 监控脚本示例7. 设置定期任务清理8. 检查配置是…

测试工程师八股文04|计算机网络 和 其他

一、计算机网络 1、http和https的区别 HTTP和HTTPS是用于在互联网上传输数据的协议。它们都是应用层协议&#xff0c;建立在TCP/IP协议栈之上&#xff0c;用于客户端&#xff08;如浏览器&#xff09;和服务器之间的通信。 ①http和https的主要区别在于安全性。http是一种明…

单片机学习笔记——入门51单片机

一、单片机基础介绍 1.何为单片机 单片机&#xff0c;英文Micro Controller Unit&#xff0c;简称MCU 。内部集成了中央处理器CPU、随机存储器ROM、只读存储器RAM、定时器/计算器、中断系统和IO口等一系列电脑的常用硬件功能 单片机的任务是信息采集&#xff08;依靠传感器&a…

【青牛科技】D8563是低功耗的CMOS实时时钟/日历电路,它提供一个可编程时钟输出,一个中断输出和掉电检测器,所有的地址和数据通过IC总线接口串行传递。

概述&#xff1a; D8563是低功耗的CMOS实时时钟/日历电路,它提供一个可编程时钟输出&#xff0c;一个中断输出和掉电检测器&#xff0c;所有的地址和数据通过IC总线接口串行传递。最大总线速度为400Kbitss每次读写数据后&#xff0c;内嵌的字地址寄存器会自动产生增量。 主要特…

安卓获取所有可用摄像头并指定预览

在Android设备中&#xff0c;做预览拍照的需求的时候&#xff0c;我们会指定 CameraSelector DEFAULT_FRONT_CAMERA前置 或者后置CameraSelector DEFAULT_BACK_CAMERA 如果你使用的是平板或者工业平板&#xff0c;那么就会遇到多摄像头以及外置摄像头问题&#xff0c;简单的指…

R语言学习笔记-1

1. 基础操作和函数 清空环境&#xff1a;rm(list ls()) 用于清空当前的R环境。 打印输出&#xff1a;print("Hello, world") 用于输出文本到控制台。 查看已安装包和加载包&#xff1a; search()&#xff1a;查看当前加载的包。install.packages("package_na…

Windows如何安装go环境,离线安装beego

一、安装go 1、下载go All releases - The Go Programming Language 通过网盘分享的文件&#xff1a;分享的文件 链接: https://pan.baidu.com/s/1MCbo3k3otSoVdmIR4mpPiQ 提取码: hxgf 下载amd64.zip文件&#xff0c;然后解压到指定的路径 2、配置环境变量 需要新建两个环境…

Mac上使用ln指令创建软链接、硬链接

在Mac、Linux和Unix系统中&#xff0c;软连接&#xff08;Symbolic Link&#xff09;和硬连接&#xff08;Hard Link&#xff09;是两种不同的文件链接方式。它们的主要区别如下&#xff1a; 区别&#xff1a; 硬连接&#xff1a; 不能跨文件系统。不能链接目录&#xff08;为…

Unity A*算法实现+演示

注意&#xff1a; 本文是对基于下方文章链接的理论&#xff0c;并最终代码实现&#xff0c;感谢作者大大的描述&#xff0c;非常详细&#xff0c;流程稍微做了些改动&#xff0c;文末有工程网盘链接&#xff0c;感兴趣的可以下载。 A*算法详解(个人认为最详细,最通俗易懂的一…

博弈论3:图游戏SG函数(Graph Games)

目录 一、图游戏是什么 1.游戏特征 2.游戏实例 二、图游戏的必胜策略 1.SG 函数&#xff08;Sprague-Grundy Function&#xff09; 2.必胜策略&#xff08;利用SG函数&#xff09; 3.拿走游戏转化成图游戏&#xff08;Take-away Game -> Graph Game&#xff09; 一、图…

0101多级nginx代理websocket配置-nginx-web服务器

1. 前言 项目一些信息需要通过站内信主动推动给用户&#xff0c;使用websocket。web服务器选用nginx&#xff0c;但是域名是以前通过阿里云申请的&#xff0c;解析ip也是阿里云的服务器&#xff0c;甲方不希望更换域名。新的系统需要部署在内网服务器&#xff0c;简单拓扑图如…

qt-C++笔记之自定义类继承自 `QObject` 与 `QWidget` 及开发方式详解

qt-C笔记之自定义类继承自 QObject 与 QWidget 及开发方式详解 code review! 参考笔记 1.qt-C笔记之父类窗口、父类控件、对象树的关系 2.qt-C笔记之继承自 QWidget和继承自QObject 并通过 getWidget() 显示窗口或控件时的区别和原理 3.qt-C笔记之自定义类继承自 QObject 与 QW…

Elastic 8.17:Elasticsearch logsdb 索引模式、Elastic Rerank 等

作者&#xff1a;来自 Elastic Brian Bergholm 今天&#xff0c;我们很高兴地宣布 Elastic 8.17 正式发布&#xff01; 紧随一个月前发布的 Elastic 8.16 之后&#xff0c;我们将 Elastic 8.17 的重点放在快速跟踪关键功能上&#xff0c;这些功能将带来存储节省和搜索性能优势…

[C++]类的继承

一、什么是继承 1.定义&#xff1a; 在 C 中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;派生类&#xff09;继承另一个类&#xff08;基类&#xff09;的成员&#xff08;数据和函数&#xff09;。继承使得派生类能够直接访问基类的公有和保护成员&#xf…

Docker 用法详解

文章目录 一、Docker 快速入门1.1 部署 MYSQL1.2 命令解读&#xff1a; 二、Docker 基础2.1 常见命令&#xff1a;2.1.1 命令介绍&#xff1a;2.1.2 演示&#xff1a;2.1.3 命令别名&#xff1a; 2.2 数据卷&#xff1a;2.2.1 数据卷简介&#xff1a;2.2.2 数据卷命令&#xff…

【自动化】Python SeleniumUtil 油猴 工具 自动安装用户脚本

【自动化】Python SeleniumUtil 油猴 工具 【自动化】Python SeleniumUtil 工具-CSDN博客【自动化】Python SeleniumUtil 工具。https://blog.csdn.net/G971005287W/article/details/144565691 油猴工具 import timefrom selenium.webdriver.support.wait import WebDriverW…

盛元广通畜牧与水产品检验技术研究所LIMS系统

一、系统概述 盛元广通畜牧与水产品检验技术研究所LIMS系统集成了检测流程管理、样品管理、仪器设备管理、质量控制、数据记录与分析、合规性管理等功能于一体&#xff0c;能够帮助实验室实现全流程的数字化管理。在水产、畜牧产品的质检实验室中&#xff0c;LIMS系统通过引入…