首先了解一下进程和线程 进程:执行任务的独立集合 分配资源的基本单位 线程:隶属于进程的一部分 分配cpu的基本单位(轮片时间片的基本单位) 线程的运行方式: 1.并发 多个执行命令交替执行 2.并行 多个执行命令同时执行 例如 : a=0; 两个线程执行 a++ 并发执行:a=2 并行执行:a=1 线程的组成部分: 1.线程栈(堆栈区):用来存放线程的临时的 局部的资源 2.内核对象:计数器(2) (句柄关闭-1,线程退出-1) 挂起计数器 信号(无):如果线程退出 会变成有信号 ;不退出 无信号 线程的挂起和恢复: 由挂起计数器控制 计数为0时才真正的执行挂起和恢复 我们想实现三个按钮控制进度条的界面
这里我们想要实现
按下go按钮 进度条增长
按下pause按钮 进度条暂停
按下stop按钮 进度条清空 杀死进程
创建线程的函数: CreateThread(0,//线程安全属性
0,//线程栈的大小 默认1MB
&TheradProc,//线程函数
this,//线程参数
0,//创建标志 0 立刻运行 CREATE_SUSPENDED 挂起
0//线程id
);
其中 线程函数TheradProc(): 我们想要的功能 要在线程函数里实现
先展示主界面包含什么
#ifndef WIDGET_H
#define WIDGET_H
#include<windows.h>
#include <QWidget>
#include<QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void signalSetValue(int );//线程函数里发射的信号 用于修改进度条
public slots:
void slotSetValue(int );//线程函数里发射的信号的槽函数 修改进度条
private slots:
void on_pushButton_clicked(); //go按钮槽函数
void on_pushButton_2_clicked();//pause按钮槽函数
void on_pushButton_3_clicked();//stop按钮槽函数
private:
Ui::Widget *ui;
public:
HANDLE hTherad;//用于记录线程的句柄
bool Mark;//stop按钮用到 用于控制线程的结束
};
#endif // WIDGET_H
我们首先为按钮go添加一个槽函数
void Widget::on_pushButton_clicked()
{
//创建线程
if(! hTherad)
{
Mark=true;
hTherad= CreateThread(
0,//线程安全属性
0,//线程栈的大小 默认1MB
&TheradProc,//线程函数
this,//线程参数
0,//创建标志 0 立刻运行 CREATE_SUSPENDED 挂起
0//线程id
);
}
else
{
ResumeThread(hTherad);
//恢复线程
//SuspendThread(hTherad); 挂起线程 1
//SuspendThread(hTherad); 挂起线程 2
//ResumeThread(hTherad); 恢复线程 1
//ResumeThread(hTherad); 恢复线程 0
}
}
在槽函数中我们添加一个线程
同时将我们的线程的句柄赋给hTherad
之后我们就可以通过hTherad对线程进行一些操作了
将创建线程的函数: CreateThread的参数 :全局线程函数TheradProc()定义出来
在TheradProc函数中 我们要对进度条进行操作
在全局函数中不识别当前的界面ui 不能直接对界面上的空间内进行操作
可以通过连接信号和槽进行间接操作
同时 我们又对其加了一个判断条件 如果线程已经被创建出来 就无须再进行创建了
只需要让进度条动起来就行
else
{
ResumeThread(hTherad);
//恢复线程
//SuspendThread(hTherad); 挂起线程 1
//SuspendThread(hTherad); 挂起线程 2
//ResumeThread(hTherad); 恢复线程 1
//ResumeThread(hTherad); 恢复线程 0
}
这里演示了挂起线程和恢复线程
如:挂起n次线程 需要恢复n次线程才能让线程动起来
DWORD WINAPI TheradProc(LPVOID lpParameter)
//线程函数是一个全局函数 不能识别ui 所以线程参数 定义为Widget*类型
{
Widget*wig=(Widget*)lpParameter;
for (int i=0;i<=100&&wig->Mark==true;i++) {
//发送信号 传i的值用于修改
emit wig->signalSetValue(i);
Sleep(100);//每隔100毫秒执行一次
}
return 0;
}
槽函数
void Widget::slotSetValue(int i)
{
//对进度条修改
ui->progressBar->setValue(i);
}
在Widget界面的构造函数进行信号的连接
connect(this,&Widget::signalSetValue,this,&Widget::slotSetValue);
到这里我们的go按钮就完成了
接下来开始编写我们的pause按钮的功能
对pause按钮添加一个槽函数
void Widget::on_pushButton_2_clicked()
{
SuspendThread(hTherad);
//挂起线程
}
只需要将线程挂起就可以达到暂停的作用
接下来 stop按钮
这里我们有三种方法来结束线程
1.正常结束
Mark=false;
if(hTherad)
{
CloseHandle(hTherad);
hTherad=NULL;
}
ui->progressBar->setValue(0);
标记赋值为false
如果句柄不等于空 就把句柄回收
缺点 :无法确保线程一定会结束 只是把句柄回收了
2.强制杀死
Mark=false;
if(hTherad)
{
TerminateThread(hTherad,-1);
CloseHandle(hTherad);
hTherad=NULL;
}
一定会使进程结束
缺点:当进程在开辟空间的过程中 突然被杀死 那么开辟的空间不会被回收 并且空间的锁无法访问 造成内存泄露
void Widget::on_pushButton_3_clicked()
{
//正常退出
Mark=false;
//强制杀死
//TerminateThread(hTherad,-1);
//3.能正常退出 则正常退出 如果不能 则强制杀死
//(线程正常退出时会发送一个信号 如果信号=WAIT_TIMEOUT则视为正常退出)
if(hTherad)
{
if(WAIT_TIMEOUT==WaitForSingleObject(hTherad,100))
TerminateThread(hTherad,-1);
CloseHandle(hTherad);
hTherad=NULL;
}
ui->progressBar->setValue(0);
}
3.结合的方式结束(一般我们用)
//正常退出
Mark=false;
//(线程正常退出时会发送一个信号 如果信号=WAIT_TIMEOUT则视为正常退出)
if(hTherad)
{
if(WAIT_TIMEOUT==WaitForSingleObject(hTherad,100))
TerminateThread(hTherad,-1);
CloseHandle(hTherad);
hTherad=NULL;
}
ui->progressBar->setValue(0);
完整代码如下