1 线程

线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享内存空间,使得系统资源消耗减少。

2 QT中多线程的实现

官方文档里说, QThread 类提供了一种独立于平台的方法来管理线程。 QThread 对象在程序中管理一个控制线程。 QThreads 在 run()中开始执行。默认情况下, run()通过调用 exec()来启动事件循环,并在线程中运行 Qt 事件循环。您可以通过使用 QObject::moveToThread()将 worker对象移动到线程来使用它们。

QThread 线程类是实现多线程的核心类。 Qt 有两种多线程的方法,其中一种是继承 QThread的 run()函数,另外一种是把一个继承于 QObject 的类转移到一个 Thread 里。 Qt4.8 之前都是使用继承 QThread 的 run()这种方法,但是 Qt4.8 之后, Qt 官方建议使用第二种方法。本文使用第一种方法,继承 QThread的 run()函数。

3 U盘的自动挂载和卸载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
* 函数名称:void CheckIsDevice()
* 函数功能:挂载U盘
* 日期:2024.07.29
*/
QString MyThreadCheckDevice::CheckIsDevice(int plug_sock)
{
char buf[2048] = {0};
recv(plug_sock, &buf, sizeof(buf), 0);

QString result = buf;
int flag = 0;
while(result.contains("add")||result.contains("remove"))
{
result = buf;
if(result.contains("add"))
{
flag = 1;
if (result.contains("sda1"))
{
system("mount /dev/sda1 /opt/SD");
sync();
printf("sda1.....mount ok!!...........\n");

}
else if (result.contains("sdb1"))
{
system("mount /dev/sdb1 /opt/SD");
sync();
printf("sdb1......mount ok!!.............\n");

}
else if (result.contains("sdc1"))
{
system("mount /dev/sdc1 /opt/SD");
sync();
printf("sdc1....mount ok!!................\n");

}

}
else if(result.contains("remove"))
{
flag = 0;
if (result.contains("sda1"))
{
system("umount /dev/sda1");
sync();
printf("---------------umount sda1-----------------------\n");
}
else if (result.contains("sdb1"))
{
system("umount /dev/sdb1");
sync();
printf("---------------umount sdb1------------------------\n");
}
else if (result.contains("sdc1"))
{
system("umount /dev/sdc1");
sync();
printf("----------------umount sdc1------------------------\n");
}
}
memset(buf,0,1024);
if (flag){
return "SDOK";
}
else
return "SDUMOUNT";
}
}

/*
* 函数名称:void init_hotplug_sock()
* 函数功能:监控U盘热插拔
* 日期:2024.07.29
*/
static int init_hotplug_sock(void)
{
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024;
int retval;
memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = 1;
int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (hotplug_sock == -1) {
printf("error getting socket: %s", strerror(errno));
return -1;
}
/* set receive buffersize */
setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
retval = bind(hotplug_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
if (retval < 0) {
printf("bind failed: %s", strerror(errno));
close(hotplug_sock);
hotplug_sock = -1;
return -1;
}
return hotplug_sock;
}

4 工程创建

主线程内有很多方法在主线程内, 但是子线程,只有 run()方法是在子线程里的。 run()方法是继承于 QThread 类的方法,用户需要重写这个方法,一般是把耗时的操作写在这个 run()方法里面。

(1)在之前的工程基础上,添加多线程应用,包括mythread.h、mythread.cpp、mythread.ui。

(2)设计ui界面

如图所示

(3)创建两个继承 QThread 的线程,其中线程1的功能是数字每一秒加一,并显示在屏幕中;线程2的功能是监控USB,实现U盘的自动挂载以及卸载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class MyThreadNum : public QThread
{
/* 用到信号槽即需要此宏定义 */
Q_OBJECT

public:
MyThreadNum(QWidget *parent) {
Q_UNUSED(parent);
}

/* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */
void run();

signals:

/* 声明一个信号,译结果准确好的信号 */
void ResultReadyNum(const QString &s);

};

class MyThreadCheckDevice : public QThread
{
/* 用到信号槽即需要此宏定义 */
Q_OBJECT

public:
MyThreadCheckDevice(QWidget *parent) {
Q_UNUSED(parent);
}

/* 重写run方法,继承QThread的类,只有run方法是在新的线程里 */
void run();
QString CheckIsDevice(int plug_sock);

signals:

/* 声明一个信号,译结果准确好的信号 */
void ResultReadyCheckDevice(const QString &s);

};

(4)实例化两个线程

1
2
3
4
5
/* 实例化两个线程 */
/* 线程1:屏幕数值每一秒加一*/
ThreadNum = new MyThreadNum(this);
/* 线程2:监控U盘挂载*/
ThreadCheckDevice = new MyThreadCheckDevice(this);

(5)重写run()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* 函数名称:void run()
* 函数功能:线程1运行
* 作者:JR.Wang
* 日期:2024.07.29
*/
void MyThreadNum::run()
{
QString result = "MyThreadNum start OK!";
while(1)
{
sleep(1);
qDebug()<<"MyThreadNum is Runing!";
emit ResultReadyNum(result);
}

}

/*
* 函数名称:void run()
* 函数功能:线程2运行
* 作者:JR.Wang
* 日期:2024.07.29
*/
void MyThreadCheckDevice::run()
{
int hotplug_sock= init_hotplug_sock();
QString result;
while(1)
{
result = CheckIsDevice(hotplug_sock);
emit ResultReadyCheckDevice(result);
}

}

(6)在mythread类中定义两个函数,用来接收线程的信号,若开启线程成功,则屏幕会有相应的动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* 函数名称:void handleResultsNum()
* 函数功能:返回线程1信息的槽函数
* 作者:JR.Wang
* 日期:2024.07.29
*/
void mythread::handleResultsNum(const QString &result)
{
static int i;
qDebug()<<result<<endl;
i++;
if(i > 100)
i = 0;
ui->lcdNumber->display(i);
}

/*
* 函数名称:void handleResultsCheckDevice()
* 函数功能:返回线程2信息的槽函数
* 作者:JR.Wang
* 日期:2024.07.29
*/
void mythread::handleResultsCheckDevice(const QString &result)
{
qDebug()<<result<<endl;
static int flag = 0;
qDebug()<<flag<<endl;
if(!result.compare("SDOK") && flag == 0){
flag = 1;
LQToast *toast = new LQToast;
toast->setAlignment(LQToast::Custom);
toast->setText("检测到U盘插入");
toast->setBackgroundColor(QColor("white"));
toast->show();
ui->LineEditThread2->setText("U盘已挂载");
}
if(!result.compare("SDUMOUNT")){
flag = 0;
LQToast *toast = new LQToast;
toast->setAlignment(LQToast::Custom);
toast->setText("U盘已拔出");
toast->setBackgroundColor(QColor("white"));
toast->show();
ui->LineEditThread2->setText("U盘已卸载");
}
}

(7)连接线程和mythread类中定义的函数

1
2
3
4
5
/* 信号槽连接 */
connect(ThreadNum, SIGNAL(ResultReadyNum(QString)),
this, SLOT(handleResultsNum(QString)));
connect(ThreadCheckDevice, SIGNAL(ResultReadyCheckDevice(QString)),
this, SLOT(handleResultsCheckDevice(QString)));

(8)使用按钮开启线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* 函数名称:void on_PbThread1_released()
* 函数功能:开启线程1按钮的槽函数
* 作者:JR.Wang
* 日期:2024.07.29
*/
void mythread::on_PbThread1_released()
{
/* 检查线程是否在运行,如果没有则开始运行 */
if (!ThreadNum->isRunning())
ThreadNum->start();
}

/*
* 函数名称:void on_PbThread2_released()
* 函数功能:开启线程2按钮的槽函数
* 作者:JR.Wang
* 日期:2024.07.29
*/
void mythread::on_PbThread2_released()
{
/* 检查线程是否在运行,如果没有则开始运行 */
if (!ThreadCheckDevice->isRunning())
ThreadCheckDevice->start();
}

5 移植到开发板运行

  • 点击线程1按钮,数字每一秒加一,并显示在屏幕中。
  • 点击线程2按钮,U盘插入会自动装载,拔出自动卸载。