Qt线程的几种使用方法

news2024/12/23 9:16:59

目录

  • 引言
  • 使用方法
    • 重写QThread::run()
    • moveToThread
    • QRunnable使用
    • QtConcurrent使用
  • 完整代码

引言

多线程不应该是一个复杂而令人生畏的东西,它应该只是程序员的一个工具,不应该是调用者过多记忆相关概念,而应该是被调用方应该尽可能的简化调用,这也是Qt多线程接口演化的方向。

目前Qt的多线程调用提供了三种方式,一种是子类化QThread后重写run函数,一种是将对象移动到特定线程中,还有一种是通过重写QRunnable的run函数搭配线程池实现,最后一种则是调用高级接口Qt::Concurrent。这三种方式在后面章节会进行详细阐述。

调用方式的简化意味者调用者能够花更多的时间去思考线程的使用是否合理。在通常情况下,我们是将耗时操作推进子线程中执行,防止主线程被阻塞,但在推入子线程之前,应该考虑原有的耗时操作是否有优化控件,能不能将其时间复杂度降低。因为移动到子线程只是解决了界面卡顿的表面问题,需要消耗的资源依然被消耗,如果是无意义的消耗会造成对整个系统的拖累。

除了需要考虑优化函数执行逻辑,还需要考虑的是限制子线程个数,不能无限制推高单个应用的线程数。一个就是线程本事需要消耗资源,另一个是切换线程和保证线程数据安全的资源消耗也是很多的,而且Qt已经提供了很方便的线程池化方案,因此限制线程数是一个有必要且不难达成的良好习惯。
在这里插入图片描述

使用方法

重写QThread::run()

/*---------------------WorkerThread-------------------------*/
class WorkerThread : public QThread
{
    Q_OBJECT
public:
    explicit WorkerThread();
protected:
    void run();
signals:
    void resultReady(const QString &s);
};

void WorkerThread::run(){
    /* ... here is the expensive or blocking operation ... */
}

/*----------------------MainWindow--------------------------*/
void MainWindow::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread();
    // Release object in workerThread
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}

以上是通过子类化QThread后重写run函数实现子线程的调用,其中只有run函数内属于子线程,也就是QThread只是一个线程管理类,而其实例本身并不是一个线程,其他的成员和run函数并不在同一线程。

需要注意的是,上述调用线程的方法已经过时,目前官方不推荐使用。

moveToThread

/*--------------------Worker-------------------------*/
class Worker : public QObject
{
    Q_OBJECT
public:
    Worker();
    ~Worker();

signals:
    void startWorking();

public slots:
    void toWork();
};

/*---------------------------Controller----------------------------*/
class Controller : public QObject
{
    Q_OBJECT

public:
    Controller();
    ~Controller();

    void toWorkDirect();

signals:
    void startWorking();

private:
    QThread worker_thread_;
    Worker *worker_;
};
Worker::Worker()
{
    qDebug() << "constructor" << thread()->currentThreadId();
}

Worker::~Worker()
{
    qDebug() << "destructor" << thread()->currentThreadId();
}

void Worker::toWork()
{
    // to do something
    qDebug() << "work function" << thread()->currentThreadId();
}

Controller::Controller() {
    worker_ = new Worker;
    worker_->moveToThread(&worker_thread_);
    connect(this, &Controller::startWorking, worker_, &Worker::toWork);
    connect(this, &Controller::startWorking, worker_, [=]{
        // lambda函数同样在子线程中
        worker_->toWork();
    });
    connect(&worker_thread_, &QThread::finished, worker_, &QObject::deleteLater);
    worker_thread_.start();
}

Controller::~Controller() {
    worker_thread_.quit();
    worker_thread_.wait();
}

void Controller::toWorkDirect()
{
    qDebug() << "work direct function";
    worker_->toWork();
}

测试代码

auto button = new QPushButton(this);
connect(button, &QPushButton::clicked, this, [=]{
    auto controller = new Controller();
    emit controller->startWorking();

    controller->toWorkDirect();

    QTimer::singleShot(1000, [=]{
        controller->deleteLater();
    });
});

以上是通过moveToThread将耗时的工作对象推入线程的方法,也是目前官方推荐的QThread使用方法。

在这里插入图片描述

上述测试代码中,仅为示例展示线程归属,并不代表实际业务。通过按钮创建controller,点击发送信号startWorking以及直接调用toWorkDirect函数,再通过定时器析构退出线程。通过上图控制台打印可以看到,构造和直接调用函数都是在主线程中,只有通过信号槽连接的函数才会在子线程中,这里可以看出Qt线程的调用方式其实通过事件循环实现的,详细可以参考之前章节。

QRunnable使用

class RunnableObject : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit RunnableObject(QObject *parent = nullptr);
    ~RunnableObject();

signals:
    void workFinished(QString message);

protected:
    void run() override {

        // to work...

        QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
        Q_EMIT workFinished(temp);
    };
};


void InitRunnable(QObject *parent)
{
    auto runnable_object_ = new RunnableObject(parent);
    auto runnable_thread_pool_ = new QThreadPool(parent);
    runnable_thread_pool_->start(runnable_object_);
}

首先RunnableObject 继承QRunnable,重写run实现需要放在线程中的函数,再通过线程池的启动函数start(),即可完成在线程池中运行。需要注意的是,默认执行结束后会销毁原来的执行对象,如果希望执行后依然保持工作对象,需要通过函数setAutoDelete(false)去设置。

QtConcurrent使用

auto temp_worker = new Worker();
QtConcurrent::run(temp_worker, &Worker::toWork);

上述代码则是通过高级接口QtConcurrent::run去实现的代码,将temp_worker的toWork函数直接推入子线程中,这样更符合调用者的直觉,相比于前两种调用方式更加简化,也是笔者推荐的线程使用方式。

auto thread_pool = new QThreadPool(this);
thread_pool->setMaxThreadCount(4);

auto temp_worker = new Worker();
QFuture<void> temp_future = QtConcurrent::run(thread_pool, temp_worker, &Worker::toWork);

上述代码是QtConcurrent::run的完整使用方法,包含线程池以及QFuture。如前文所述,Qt的线程池化方案非常简便,设置最大线程数即可。QFuture主要搭配QFutureWatcher使用,用于监控函数的返回值,详细可参考官方文档。

完整代码

示例声明

#ifndef EXAMPLEWIDGET4_HPP
#define EXAMPLEWIDGET4_HPP

#include <QWidget>

class QThread;
class QThreadPool;
class QTextEdit;
class QPushButton;
class ThreadObject;
class WorkerObject;
class RunnableObject;
class ExampleWidget4 : public QWidget
{
    Q_OBJECT
public:
    explicit ExampleWidget4(QWidget *parent = nullptr);
    ~ExampleWidget4();

signals:

private:
    void InitThreadRun();
    void InitMoveToThread();
    void InitRunnable();
    void InitConcurrent();

private slots:
    void toWorkDirect();

private:
    QTextEdit *text_edit_;
    QPushButton *thread_btn_;
    QPushButton *move_thread_btn1_;
    QPushButton *move_thread_btn2_;
    QPushButton *move_thread_btn3_;
    QPushButton *runnable_btn1_;
    QPushButton *runnable_btn2_;
    QPushButton *concurrent_btn1_;
    QPushButton *concurrent_btn2_;

    // QThread::run
    ThreadObject *thread_object_;

    // moveToThread
    QThread *worker_thread_;
    WorkerObject *worker_object_;

    // QRunnable
    RunnableObject *runnable_object_;
    QThreadPool *runnable_thread_pool_;

    // QtConcurrent
    WorkerObject *concurrent_worker_;
    QThreadPool *concurrent_thread_pool_;
};

#endif // EXAMPLEWIDGET1_HPP

示例实现

#include "example_widget4.hpp"
#include "thread_object.hpp"
#include "worker_object.hpp"
#include "runnable_object.hpp"

#include <QLabel>
#include <QTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QtConcurrent>

ExampleWidget4::ExampleWidget4(QWidget *parent)
    : QWidget{parent}
{
    auto thread_id = new QLabel(this);
    thread_id->setText(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));

    text_edit_ = new QTextEdit(this);
    text_edit_->setReadOnly(true);

    auto clear_btn = new QPushButton("clear", this);
    connect(clear_btn, &QPushButton::clicked, this, [=] {
        text_edit_->clear();
    });

    auto main_btn = new QPushButton("Main", this);
    connect(main_btn, &QPushButton::clicked, this, [=] {
        text_edit_->append(QString("Main Thread: ") + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16));
    });

    InitThreadRun();
    InitMoveToThread();
    InitRunnable();
    InitConcurrent();

    // 布局
    auto lable_layout = new QHBoxLayout;
    lable_layout->addWidget(thread_id);
    lable_layout->addStretch();
    lable_layout->addWidget(clear_btn);

    auto move_thread_layout = new QHBoxLayout;
    move_thread_layout->addWidget(move_thread_btn1_);
    move_thread_layout->addWidget(move_thread_btn2_);
    move_thread_layout->addWidget(move_thread_btn3_);

    auto runnable_layout = new QHBoxLayout;
    runnable_layout->addWidget(runnable_btn1_);
    runnable_layout->addWidget(runnable_btn2_);

    auto concurrent_layout = new QHBoxLayout;
    concurrent_layout->addWidget(concurrent_btn1_);
    concurrent_layout->addWidget(concurrent_btn2_);

    auto button_layout = new QVBoxLayout;
    button_layout->addWidget(main_btn);
    button_layout->addWidget(thread_btn_);
    button_layout->addLayout(move_thread_layout);
    button_layout->addLayout(runnable_layout);
    button_layout->addLayout(concurrent_layout);

    auto main_layout = new QVBoxLayout(this);
    main_layout->addLayout(lable_layout);
    main_layout->addWidget(text_edit_);
    main_layout->addLayout(button_layout);
}

ExampleWidget4::~ExampleWidget4()
{
    thread_object_->quit();
    thread_object_->wait();

    worker_thread_->quit();
    worker_thread_->wait();

    runnable_thread_pool_->waitForDone();

    concurrent_thread_pool_->waitForDone();
}

void ExampleWidget4::InitThreadRun()
{
    // QThread重写run函数
    thread_object_ = new ThreadObject(this);
    connect(thread_object_, &ThreadObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    thread_btn_ = new QPushButton("Thread Object Run", this);
    connect(thread_btn_, &QPushButton::clicked, thread_object_, [this] {
        thread_object_->start();
    });
}

void ExampleWidget4::InitMoveToThread()
{
    // moveToThread
    worker_thread_ = new QThread(this);
    worker_object_ = new WorkerObject;// moveToThread的WorkerObject不能有父对象
    worker_object_->moveToThread(worker_thread_);
    connect(worker_object_, &WorkerObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });
    connect(worker_thread_, &QThread::finished, worker_object_, &QObject::deleteLater);
    worker_thread_->start();

    move_thread_btn1_ = new QPushButton("Move to Thread Method1", this);
    connect(move_thread_btn1_, &QPushButton::clicked, worker_object_, &WorkerObject::toWork);

    move_thread_btn2_ = new QPushButton("Move to Thread Method2", this);
    connect(move_thread_btn2_, &QPushButton::clicked, worker_object_, [this] {
        worker_object_->toWork();
    });

    move_thread_btn3_ = new QPushButton("Move to Thread Method3", this);
    connect(move_thread_btn3_, &QPushButton::clicked, this, &ExampleWidget4::toWorkDirect);
}

void ExampleWidget4::InitRunnable()
{
    runnable_object_ = new RunnableObject(this);
    runnable_object_->setAutoDelete(false);// autoDelete()为trun,QThreadPool取得的所有权并自动删除它
    connect(runnable_object_, &RunnableObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    runnable_thread_pool_ = new QThreadPool(this);
    runnable_thread_pool_->setMaxThreadCount(1);

    runnable_btn1_ = new QPushButton("Runnable1", this);
    connect(runnable_btn1_, &QPushButton::clicked, this, [this] {
        runnable_thread_pool_->start(runnable_object_);
    });

    runnable_btn2_ = new QPushButton("Runnable2", this);
    connect(runnable_btn2_, &QPushButton::clicked, this, [this] {
        auto temp_runnable = new RunnableObject;
        connect(temp_runnable, &RunnableObject::workFinished, this, [this](QString message) {
            text_edit_->append(message);
        });

        QThreadPool::globalInstance()->start(temp_runnable);
    });
}

void ExampleWidget4::InitConcurrent()
{
    // QtConcurrent
    concurrent_worker_ = new WorkerObject(this);
    connect(concurrent_worker_, &WorkerObject::workFinished, this, [this](QString message) {
        text_edit_->append(message);
    });

    concurrent_thread_pool_ = new QThreadPool(this);
    concurrent_thread_pool_->setMaxThreadCount(1);

    concurrent_btn1_ = new QPushButton("Concurrent1", this);
    connect(concurrent_btn1_, &QPushButton::clicked, this, [this] {
        QtConcurrent::run(concurrent_thread_pool_, concurrent_worker_, &WorkerObject::toWork);
    });

    concurrent_btn2_ = new QPushButton("Concurrent2", this);
    connect(concurrent_btn2_, &QPushButton::clicked, this, [this] {
        // 默认使用应用程序公共线程池QThreadPool::globalInstance()
        QtConcurrent::run(concurrent_worker_, &WorkerObject::toWork);
    });
}

void ExampleWidget4::toWorkDirect()
{
    worker_object_->toWork();
}


ThreadObject声明

#ifndef THREADOBJECT_HPP
#define THREADOBJECT_HPP

#include <QThread>

class ThreadObject : public QThread
{
    Q_OBJECT
public:
    explicit ThreadObject(QObject *parent = nullptr);

signals:
    void workFinished(QString message);

protected:
    void run() override;
};

#endif // THREADOBJECT_HPP

ThreadObject实现

#include "thread_object.hpp"

ThreadObject::ThreadObject(QObject *parent)
    : QThread{parent}
{

}

void ThreadObject::run()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

WorkerObject声明

#ifndef WORKEROBJECT_HPP
#define WORKEROBJECT_HPP

#include <QObject>
#include <QThread>

class WorkerObject : public QObject
{
    Q_OBJECT
public:
    explicit WorkerObject(QObject *parent = nullptr);

signals:
    void workFinished(QString message);

public slots:
    void toWork();
};

#endif // WORKEROBJECT_HPP

WorkerObject实现

#include "worker_object.hpp"

#include <QThread>

WorkerObject::WorkerObject(QObject *parent)
    : QObject{parent}
{

}

void WorkerObject::toWork()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

RunnableObject声明

#ifndef RUNNABLEOBJECT_HPP
#define RUNNABLEOBJECT_HPP

#include <QObject>
#include <QRunnable>

class RunnableObject : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit RunnableObject(QObject *parent = nullptr);
    ~RunnableObject();

signals:
    void workFinished(QString message);

protected:
    void run() override;
};

#endif // RUNNABLEOBJECT_HPP

RunnableObject实现

#include "runnable_object.hpp"

#include <QThread>

RunnableObject::RunnableObject(QObject *parent)
    : QObject{parent}
{

}

RunnableObject::~RunnableObject()
{

}

void RunnableObject::run()
{
    QString temp = QString(__FUNCTION__) + " 0x" + QString::number((qint64)QThread::currentThreadId(), 16);
    Q_EMIT workFinished(temp);
}

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

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

相关文章

Java网络开发(Tomcat)——登陆和注册功能 的 迭代升级 从Jsp到JavaScript + axios + vue 同步到异步

目录 引出前置工作vueaxiosresp0.vue版本的jsp模板1.导包--Json&#xff1a;pom.xml文件&#xff1a;2.新建一个专门用来处理响应的实体类ResData3.在axios中&#xff0c;所有响应必须是 resp.getWriter().write() 的方式&#xff0c;核心代码如下4.在jsp前端代码中导包&#x…

浅谈一级机电管道设计中的压力与介质温度

管道设计是工程设计中的一个非常重要的部分&#xff0c;管道的设计需要考虑到许多因素&#xff0c;其中就包括管道设计压力分类和介质温度分类。这两个因素是在设计管道时必须非常严格考虑的&#xff0c; 首先是管道设计压力分类。在管道设计中&#xff0c;根据工作要求和要传输…

详解 Ansible 自动化运维,提升工作效率

概要 Ansible 是一个模型驱动的配置管理器&#xff0c;支持多节点发布、远程任务执行。默认使用 SSH 进行远程连接。无需在被管理节点上安装附加软件&#xff0c;可使用各种编程语言进行扩展。 一、Ansible基本架构 上图为ansible的基本架构&#xff0c;从上图可以了解到其由以…

算法刷题-关于链表,你该了解这些!

关于链表&#xff0c;你该了解这些&#xff01; 什么是链表&#xff0c;链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域&#xff08;存放指向下一个节点的指针&#xff09;&#xff0c;最后一个节点的指针域…

mybatis-plus分页查询(springboot中实现单表和多表查询)

一、mybatis-plus单表查询 使用mybatis-plus实现单表分页查询 非常方便&#xff0c;主要操作步骤如下&#xff1a; 配置分页查询拦截器进行分页查询 1.首先&#xff0c;打开mybatis-plus官网的插件&#xff08;插件主体&#xff09; 或者点击mybatis-plus插件 我是配置在s…

KameAI:探索AI驱动的未来,体验聊天GPT与AI绘画的奇妙世界

人工智能的崛起与发展随着科技的飞速发展&#xff0c;人工智能(AI)已经逐渐成为我们生活中不可或缺的一部分。它的出现不仅改变了我们与世界的互动方式&#xff0c;还为各行各业带来巨大的便利。今天&#xff0c;我们就来聊一聊一个类似ChatGPT的人工智能网站—KameAI&#xff…

Nautilus Chain全球行分享会,上海站圆满举办

在北京时间 6 月 9 日&#xff0c;由 Nautilus Chain 主办的“Layer3 模块化区块链的发展探讨”为主题的全球行活动&#xff0c;在上海顺利举办&#xff0c;本次分享会联合主办方还包 括 Stanford Blockchain Accelerator、Zebec Protocol、Tiger VC DAO、Crypto PHD、Rootz L…

Nginx【反向代理负载均衡动静分离】--上

Nginx【反向代理负载均衡动静分离】–上 先看2 个实际需求&#xff0c;引出Nginx 需求1: 访问不同微服务 示意图 需求2: 轮询访问服务 示意图 解决方案: Nginx 反向代理 负载均衡 动静分离 高可用集群 Nginx 在分布式微服务架构的位置 基本介绍 Nginx 是什么? 能干什…

solr快速上手:配置IK中文分词器(七)

0. 引言 solr作为搜索引擎&#xff0c;常用在我们对于搜索速度有较高要求且大数据量的业务场景&#xff0c;我们之前已经配置过英文分词器&#xff0c;但是针对中文分词不够灵活和实用&#xff0c;要实现真正意义上的中文分词&#xff0c;还需要单独安装中文分词器 solr快速上…

【shell 基础13】输入输出与重定向

文章目录 一. 标准输入和标准输出二、重定向1. 定义2. 输出的重定向3. 对标准错误输出重定向4. 输入的重定向 一. 标准输入和标准输出 linux中有三种标准输入输出&#xff0c;分别是STDIN&#xff0c;STDOUT&#xff0c;STDERR&#xff0c;文件描述符分别是 0、1、2。 当运行…

Android Paging3分页+ConcatAdapter+空数据视图+下拉刷新(SwipeRefreshLayout)+加载更多+错误重试 (示例)

文章目录 引入库数据模型定义分页 adapter加载更多 adapter空数据 adapter分页数据源ViewModel 提供加载数据源的方法结合以上实现的 Fragment数据重复问题 引入库 implementation androidx.paging:paging-runtime-ktx:3.1.1paging 库&#xff0c;目前还是有点小bug &#xff…

Java开发技巧-数据结构-使用HashSet判断主键是否存在、使用Pair成对结果返回/Triple三个对象返回

场景 Java中使用HashSet判断主键是否存在 HashSet实现Set接口&#xff0c;由哈希表&#xff08;实际上是HashMap&#xff09;实现&#xff0c;但不保证set的迭代顺序&#xff0c;并允许使用null元素。 HashSet的时间复杂度跟HashMap一致&#xff0c;如果没有哈希冲突则时间复…

EXCEL函数笔记1(数学函数、文本函数、日期函数)

数学函数 取整&#xff1a;INT(number) 取余&#xff1a;MOD(number,除数) 四舍五入&#xff1a;ROUND(number&#xff0c;保留几位小数) 取绝对值&#xff1a;ABS(number) 根号处理&#xff1a;SQRT&#xff08;number&#xff09; 0到1随机数&#xff1a;RAND&#xff08;&am…

Python神器Anaconda图文安装教程

来源&#xff1a;投稿 作者&#xff1a;Fairy 编辑&#xff1a;学姐 Anaconda简介 Anaconda是一种数据科学和机器学习的开发环境&#xff0c;它包含了大量的Python包、工具和库&#xff0c;以及可视化界面和集成开发环境。「Anaconda可以方便地管理Python环境和安装第三方软件…

⑧电子产品拆解分析-1拖4USB拓展坞

⑧电子产品拆解分析-1拖4USB拓展坞 一、功能介绍二、电路分析以及器件作用1、内部电路拆解 三、参考资料学习 一、功能介绍 ①USB2.0一拖四通讯&#xff1b;②具备OTG功能&#xff0c;可适配大部分USB接口设备&#xff1b; 二、电路分析以及器件作用 1、内部电路拆解 分析&am…

【分布式存储】聊一下分布式存储中分片机制

为什么需要分片 在服务端领域&#xff0c;主要特点是支撑7*24小时不间断的服务&#xff0c;而最终对各种行为会生产对应的数据&#xff0c;比如用户登陆/注册&#xff0c;发起订单交易、支付、身份验证&#xff0c;短信验证等情况都需要存储起来&#xff0c;其中包括各种各样的…

浏览器工作原理分析与首屏加载

正文 1. 页面加载时间线 我们先来一个老生常谈的面试题&#xff1a;从输入 URL 到页面加载完成的过程中都发生了什么事情&#xff1f; 这个面试题本身也是一个开放题&#xff0c;不同方向的工程师侧重也不一样。大抵的过程可以简化为&#xff1a; st>start: 输入URL e>…

WPS表格处理

wps表格中公式出来的内容如何转为纯文本 选中公式算出的结果区域&#xff0c;复制&#xff0c;在原区域上右键&#xff0c;选择性粘贴为数值&#xff0c;就转成文本了&#xff0c;当然公式也就消除了。 wps表格如何设置整列公式&#xff1f; 1、先来看看下面这个例子需做出商…

图像分割算法

文章目录 前言1. 基于区域的分割方法1.1 区域生长算法1.2 区域分裂合并算法1.3 分水岭算法1.3.1 分水岭算法原理1.3.2 opencv-python中分水岭算法的应用 2. 基于图的分割方法2.1 Grabcut图像分割 源码仓库地址 前言 图像分割是指将图像分成若干互不重叠的子区域&#xff0c;使…

如何卸载MySQL数据库以及删除所有有关信息

目录 前言 第一步 卸载mysql程序 第二步 删除安装目录的mysql项目 第三步 删除MySQL的相关注册表 第四步 删除C盘下的 C:\ProgramData\MySQL所有的文件 第五步 删除 C:\Documents and Settings\All Users\Application Data\MySQL 下的文件夹 第六步 重启电脑 尾语 前言…