当前位置: 首页 > news >正文

13-4_Qt 5.9 C++开发指南_基于QWaitCondition 的线程同步_Wait

在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。例如上一小节讲到的3个线程的例子中,假设 threadDAQ 写满一个缓冲区之后,threadShow 和 threadSaveFile 才能对缓冲区进行读操作。前面采用的互斥量和基于 OReadWriteLock 的方法都是对资源的锁定和解锁,避免同时访问资源时发生冲突。在一个线程解锁资源后,不能及时通知其他线程。(此处类似于C++中的条件变量,具体可参考:C++新特性36_条件变量的使用(介绍C++11中条件变量的用法;条件变量必须搭配互斥体和条件使用;条件、条件变量、互斥体三胞胎需要同时使用))

QWaitCondition 提供了另外一种改进的线程同步方法,QWaitCondition 与QMutex 结合,可以使一个线程在满足一定条件时通知其他多个线程,使它们及时作出响应,这样比只使用互斥量效率要高一些。例如,threadDAQ 在写满一个缓冲区之后,及时通知 threadShow 和threadSaveFile,使它们可以及时读取缓冲区数据。

QWaitCondition 提供如下一些函数:

  • wait(QMutex *lockedMutex),解锁互斥量 lockedMutex,并阻塞等待唤醒条件,被唤醒后锁定lockedMutex 并退出函数;
  • wakeAll(),唤醒所有处于等待状态的线程,线程唤醒的顺序不确定,由操作系统的调度策略决定:
  • wakeOne(),唤醒一个处于等待状态的线程,唤醒哪个线程不确定,由操作系统的调度策略决定。

QWaitCondition 一般用于“生产者/消费者”(producer/consumer)模型中。“生产者”产生数据,“消费者”使用数据,前述的数据采集、显示与存储的三线程例子就适用这种模型。

文章目录

  • 1. 实例分析
  • 2. 源码
    • 2.1 dialog.h
    • 2.2 dialog.cpp
    • 2.3 qmythread.h
    • 2.4 qmythread.cpp

1. 实例分析

创建实例程序 samp13_4,将掷骰子的程序修改为 producer/consumer 模型,一个线程类QThreadProducer 专门负责掷骰子产生点数:一个线程类QThreadConsumer 专门及时读取数据,并送给主线程进行显示。这两个类定义在一个文件 qmythread.h 里,定义代码如下:

#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include    <QObject>
#include    <QThread>class QThreadProducer : public QThread
{Q_OBJECT
private:bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;
public:QThreadProducer();void    stopThread();
};class QThreadConsumer : public QThread
{Q_OBJECT
private:bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;
public:QThreadConsumer();void    stopThread();
signals:void    newValue(int seq,int diceValue);
};
#endif // QMYTHREAD_H

QThreadProducer用于掷骰子,但是去掉了开始和暂停的功能,线程一启动就连续地掷骰子。QThreadConsumer用于读取掷骰子的次数和点数,并用发射信号方式把数据传递出去。这两个类的实现代码在一个文件qmythread.cpp里,下面是这两个类的实现代码的主要部分:

QMutex  mutex;
QWaitCondition  newdataAvailable;int     seq=0;//序号
int     diceValue;void QThreadProducer::run()
{m_stop=false;//启动线程时令m_stop=falseseq=0;qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{mutex.lock();diceValue=qrand(); //获取随机数diceValue=(diceValue % 6)+1;seq++;mutex.unlock();newdataAvailable.wakeAll();//唤醒所有线程,有新数据了msleep(500); //线程休眠100ms}
}void QThreadConsumer::run()
{m_stop=false;//启动线程时令m_stop=falsewhile(!m_stop)//循环主体{mutex.lock();newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutexemit    newValue(seq,diceValue);mutex.unlock();
//        msleep(100); //线程休眠100ms}}

掷骰子的次数和点数的变量定义为共享变量,这样两个线程都可以访问。定义了互斥量mutex,定义了QWaitCondition实例newdataAvailable,表示有新数据可用了。

QThreadProducer.:run()函数负责每隔 500 毫秒掷骰子产生一次数据,新数据产生后通过等待条件唤醒所有等待的线程,即:

newdataAvailable.wakeAll();//唤醒所有线程,有新数据了

QThreadConsumer::run()函数中的while循环,首先需要将互斥量锁定,再执行下面的一条语句:

newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutex

这条语句以 mutex 作为输入参数,内部会首先解锁 mutex,使其他线程可以使用 mutex,newdataAvailable 进入等待状态。当 QThreadProducer 产生新数据使用 newdataAvailable.wakeAll()唤醒所有线程后,newdataAvailablewait(&mutex)会再次锁定 mutex,然后退出阻塞状态,以执行后面的语句。

所以,使用 QWaitCondition 可以使 QThreadConsumer 线程的执行过程进入等待状态。在QThreadProducer 线程满足条件后,唤醒QThreadConsumer 线程及时退出等待状态,继续执行后面的程序。

使用QThreadProducer和QThreadConsumer 实现掷骰子的实例程序samp13_4运行时界面如图13-2 所示,与实例 samp13_1 的运行界面类似,只是取消了开始和暂停掷假子的按钮,下方的状态标签显示了两个线程的状态。
在这里插入图片描述
窗口的 Dialog 类的定义如下(省略了按钮槽函数等一些不重要的部分):

#ifndef DIALOG_H
#define DIALOG_H#include    <QDialog>
#include    <QTimer>#include    "qmythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QThreadProducer   threadProducer;QThreadConsumer   threadConsumer;
protected:void    closeEvent(QCloseEvent *event);
public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:void    onthreadA_started();void    onthreadA_finished();void    onthreadB_started();void    onthreadB_finished();void    onthreadB_newValue(int seq, int diceValue);...private:Ui::Dialog *ui;
};#endif // DIALOG_H

这里主要是定义了两个线程的实例,并定义了几个自定义槽函数。采用信号与槽的方式与threadConsumer建立通信并获取数据。Dialog 的构造函数主要完成信号与槽函数的关联,5 个自定义槽函数的代码与实例 samp13_1 的相同或相似,这几个函数的代码不再详细列出。

"启动线程"按钮的代码如下:

void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);
}

两个线程启动的先后顺序不应调换,应先启动 threadConsumer,使其先进入 wait 状态,后启动 threadProducer,这样在threadProducer 里 wakeAll()时 threadConsumer 就可以及时响应,否则会丢失一次掷骰子的数据。

“结束线程”按钮的代码如下:

void Dialog::on_btnStopThread_clicked()
{//结束线程threadProducer.stopThread();//结束线程的run()函数执行threadProducer.wait();////    threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);
}

结束线程时,若按照上面的顺序先结束 threadProducer 线程,则必须使用terminate()来强制结束threadConsumer 线程,因为 threadConsumer 可能还处于条件等待的阻塞状态中,将无法正常结束线程。

2. 源码

2.1 dialog.h

#ifndef DIALOG_H
#define DIALOG_H#include    <QDialog>
#include    <QTimer>#include    "qmythread.h"namespace Ui {
class Dialog;
}class Dialog : public QDialog
{Q_OBJECTprivate:QThreadProducer   threadProducer;QThreadConsumer   threadConsumer;
protected:void    closeEvent(QCloseEvent *event);
public:explicit Dialog(QWidget *parent = 0);~Dialog();private slots:void    onthreadA_started();void    onthreadA_finished();void    onthreadB_started();void    onthreadB_finished();void    onthreadB_newValue(int seq, int diceValue);void on_btnClear_clicked();void on_btnStopThread_clicked();void on_btnStartThread_clicked();private:Ui::Dialog *ui;
};#endif // DIALOG_H

2.2 dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"void Dialog::closeEvent(QCloseEvent *event)
{//关闭窗口if (threadProducer.isRunning()){threadProducer.stopThread();threadProducer.wait();}if (threadConsumer.isRunning()){threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//}event->accept();
}Dialog::Dialog(QWidget *parent) :  QDialog(parent),    ui(new Ui::Dialog)
{ui->setupUi(this);connect(&threadProducer,SIGNAL(started()),this,SLOT(onthreadA_started()));connect(&threadProducer,SIGNAL(finished()),this,SLOT(onthreadA_finished()));connect(&threadConsumer,SIGNAL(started()),this,SLOT(onthreadB_started()));connect(&threadConsumer,SIGNAL(finished()),this,SLOT(onthreadB_finished()));connect(&threadConsumer,SIGNAL(newValue(int,int)),this,SLOT(onthreadB_newValue(int,int)));
}Dialog::~Dialog()
{delete ui;
}void Dialog::onthreadA_started()
{ui->LabA->setText("Thread Producer状态: started");
}void Dialog::onthreadA_finished()
{ui->LabA->setText("Thread Producer状态: finished");
}void Dialog::onthreadB_started()
{ui->LabB->setText("Thread Consumer状态: started");
}void Dialog::onthreadB_finished()
{ui->LabB->setText("Thread Consumer状态: finished");
}void Dialog::onthreadB_newValue(int seq,int diceValue)
{QString  str=QString::asprintf("第 %d 次掷骰子,点数为:%d",seq,diceValue);ui->plainTextEdit->appendPlainText(str);QPixmap pic;QString filename=QString::asprintf(":/dice/images/d%d.jpg",diceValue);pic.load(filename);ui->LabPic->setPixmap(pic);
}void Dialog::on_btnClear_clicked()
{ui->plainTextEdit->clear();
}void Dialog::on_btnStopThread_clicked()
{//结束线程threadProducer.stopThread();//结束线程的run()函数执行threadProducer.wait();////    threadConsumer.stopThread();//结束线程的run()函数执行threadConsumer.terminate(); //因为threadB可能处于等待状态,所以用terminate强制结束threadConsumer.wait();//ui->btnStartThread->setEnabled(true);ui->btnStopThread->setEnabled(false);
}void Dialog::on_btnStartThread_clicked()
{//启动线程threadConsumer.start();threadProducer.start();ui->btnStartThread->setEnabled(false);ui->btnStopThread->setEnabled(true);
}

2.3 qmythread.h

#ifndef QMYTHREAD_H
#define QMYTHREAD_H//#include    <QObject>
#include    <QThread>class QThreadProducer : public QThread
{Q_OBJECT
private:bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;
public:QThreadProducer();void    stopThread();
};class QThreadConsumer : public QThread
{Q_OBJECT
private:bool    m_stop=false; //停止线程
protected:void    run() Q_DECL_OVERRIDE;
public:QThreadConsumer();void    stopThread();
signals:void    newValue(int seq,int diceValue);
};
#endif // QMYTHREAD_H

2.4 qmythread.cpp

#include    "qmythread.h"
#include    <QWaitCondition>
#include    <QTime>
#include    <QMutex>QMutex  mutex;
QWaitCondition  newdataAvailable;int     seq=0;//序号
int     diceValue;QThreadProducer::QThreadProducer()
{}void QThreadProducer::stopThread()
{QMutexLocker  locker(&mutex);m_stop=true;
}void QThreadProducer::run()
{m_stop=false;//启动线程时令m_stop=falseseq=0;qsrand(QTime::currentTime().msec());//随机数初始化,qsrand是线程安全的while(!m_stop)//循环主体{mutex.lock();diceValue=qrand(); //获取随机数diceValue=(diceValue % 6)+1;seq++;mutex.unlock();newdataAvailable.wakeAll();//唤醒所有线程,有新数据了msleep(500); //线程休眠100ms}
}void QThreadConsumer::run()
{m_stop=false;//启动线程时令m_stop=falsewhile(!m_stop)//循环主体{mutex.lock();newdataAvailable.wait(&mutex);//会先解锁mutex,使其他线程可以使用mutexemit    newValue(seq,diceValue);mutex.unlock();
//        msleep(100); //线程休眠100ms}}QThreadConsumer::QThreadConsumer()
{}void QThreadConsumer::stopThread()
{QMutexLocker  locker(&mutex);m_stop=true;
}

相关文章:

13-4_Qt 5.9 C++开发指南_基于QWaitCondition 的线程同步_Wait

在多线程的程序中&#xff0c;多个线程之间的同步实际上就是它们之间的协调问题。例如上一小节讲到的3个线程的例子中&#xff0c;假设 threadDAQ 写满一个缓冲区之后&#xff0c;threadShow 和 threadSaveFile 才能对缓冲区进行读操作。前面采用的互斥量和基于 OReadWriteLock…...

STM32(HAL)多串口进行重定向(printf函数发送数据)

目录 1、简介 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 串口外设配置 2.3 项目生成 3、KEIL端程序整合 4、效果测试 1、简介 在HAL库中&#xff0c;常用的printf函数是无法使用的。本文通过重映射实现在HAL库多个串口可进行类似printf函数的操作。 2.1 基础配置 2.…...

29_互联网(The Internet)(IP数据包;UDP;TCP;DNS;OSI)

上篇介绍了计算机网络的基础知识&#xff0c;也提到互联网&#xff08;The Internet&#xff09;&#xff0c;本篇将会详细介绍互联网&#xff08;The Internet&#xff09;。 文章目录 1. 互联网&#xff08;The Internet&#xff09;组成及数据包传输过程2. IP 数据包的不足3…...

xShell常用命令

xShell常用命令 一、文件夹目录1、cd-更改目录2、mkdir-建立目录3、rm-删除目录4、pwd-查看当前路径5、rmdir-删除空目录 二、文件操作1、cat-显示文件内容2、diff-比较文件内容3、查看文件的名字和后缀4、ls-列出文件5、cp-复制文件6、mv-移动和重命名文件找不同&#xff1a;选…...

React性能优化之Memo、useMemo

文章目录 React.memo两种方式参数应用场景 拓展useMemouseMemo(calculateValue, dependencies) 参考资料 React.memo React 的渲染机制&#xff0c;组件内部的 state 或者 props 一旦发生修改&#xff0c;整个组件树都会被重新渲染一次&#xff0c;即时子组件的参数没有被修改&…...

IDEA开启并配置services窗口

前言&#xff1a; 一般一个spring cloud项目中大大小小存在几个十几个module编写具体的微服务项目。此时&#xff0c;如果要调试测需要依次启动各个项目比较麻烦。 方法一&#xff1a; 默认第一次打开项目的时候&#xff0c;idea会提示是否增加这个选项卡&#xff0c;如果你没…...

vue2企业级项目(三)

vue2企业级项目&#xff08;三&#xff09; 引入mockjs&#xff0c;i18n 1、mockjs 项目下载依赖 npm install --save-dev mock根目录创建mock文件夹&#xff0c;并创建mock/index.js import Mock from "mockjs";// 设置全局延时 没有延时的话有时候会检测不到数据…...

QT 在label上透明绘图

一、新建TransparentDemo工程 二、在界面上添加label&#xff0c;修改样式表&#xff0c;将底色置为红色&#xff0c;作为北京 三、新建一个TransparentLabel类&#xff0c;继承自QLabel 此时&#xff0c;工程包括文件 五、在transparentlabel.h中添加 头文件 #include …...

SAM(Segment Anything)大模型论文汇总

A Comprehensive Survey on Segment Anything Model for Vision and Beyond 论文&#xff1a;https://arxiv.org/abs/2305.08196 25页综述&#xff0c;198篇参考文献&#xff01;52个开源项目&#xff01;本文第一个全面回顾了分割一切模型(SAM)的研究和应用进展&#xff0c;…...

金融翻译难吗,如何做好金融翻译?

我们知道&#xff0c;金融翻译涉及企业经济这块的&#xff0c;是影响各公司发展很重要的一方面&#xff0c;翻译做得好&#xff0c;可以促进公司内外的交流&#xff0c;及时掌握各种信息&#xff0c;做好应对。那么&#xff0c;金融翻译难吗&#xff0c;如何做好金融翻译&#…...

Java面试题(Tomcat与Nginx)

Tomcat 什么是Tomcat&#xff1f; 简单来说是一个运行Java的网络服务器&#xff0c;也是jsp和serlvet的一个容器 Tomcat的缺省端口是多少&#xff0c;怎么修改? conf文件夹下修改server.xml文件 <Connector connectionTimeout"20000" port"8080" p…...

React-使用mobx

React 中使用 mobx 配置开发环境 安装mobx和中间件工具 mobx-react-lite 只能函数组件中使用 yarn add mobx mobx-react-lite初始化 mobx 定义数据状态 state在构造器中实现数据响应式处理 makeAutoObservble定义修改数据的函数 action实例化 store 并导出 import { compute…...

LeetCode ACM模式——哈希表篇(一)

刷题顺序及部分思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 部分思路来源于力扣官方题解&#xff0c;作者主页&#xff1a;https://leetcode.cn/u/leetcode-solution/ 242. 有效的字母异位词 给定两个字符串 s 和 t &#xff0c;编写一个…...

WPF实战学习笔记31-登录界面全局通知

UI添加消息聚合器 <md:Snackbarx:Name"LoginSnakeBar"Grid.ColumnSpan"2"Panel.ZIndex"1"MessageQueue"{md:MessageQueue}" />注册提示消息 文件&#xff1a;Mytodo.Views.LoginView.cs构造函数添加内容 //注册提示消息 aggre…...

通用商城项目(中)

金山编译器出问题了。下面段落标号全出问题了&#xff0c;排版也出问题了。懒得改了。 使用对象存储OSS&#xff0c;保存品牌logo 新建Module&#xff0c;提供上传、显示服务 有些不明所以的&#xff0c;按照steinliving-commodity配置了一通pom.xml 新建application.yml&…...

谨慎使用JSON.stringify

谨慎使用JSON.stringify 为了避免因为对象是引用类型而造成的数据源污染&#xff0c;我们通常使用 JSON.stringify 将其转换为字符串&#xff0c;而后通过JSON.parse方法将字符串转化一个新对象来实现深拷贝。但是在这个过程中也会存在一些问题&#xff0c;本文就介绍一下使用…...

驱动开发day8

编写LED灯的驱动&#xff0c;使用GPIO子系统&#xff0c;里面添加按键的中断处理 1.应用程序发送指令控制LED亮灭 2.按键1 按下&#xff0c;led1电位反转 按键2按下&#xff0c;led2电位反转 按键3 按下&#xff0c;led3电位反转 驱动程序 #include <linux/init.h> #i…...

CAS 机制

问题分析与思考&#xff1a; CAS 是 Java 中 Unsafe 类里面的方法&#xff0c;它的全称是 CompareAndSwap&#xff0c;比较并交换 的意思。 它的主要功能是能够保证在多线程环境下&#xff0c;对于共享变量的修改的原子性。 举个例子&#xff0c;比如说有这样一个场景&#xff…...

#P1003. [NOIP2009普及组] 道路游戏

题目描述 小新正在玩一个简单的电脑游戏。 游戏中有一条环形马路&#xff0c;马路上有 nn 个机器人工厂&#xff0c;两个相邻机器人工厂之间由一小段马路连接。小新以某个机器人工厂为起点&#xff0c;按顺时针顺序依次将这 nn 个机器人工厂编号为 1\sim n1∼n&#xff0c;因…...

python-网络爬虫.regular

regular 正则表达式 (regular expression) 正则表达式(regular expression)描述了一种字符串匹配的模式 &#xff08;pattern&#xff09;&#xff0c; 可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串 中取出符合某个条件的子串等。 正则表达式是由普通…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...