让线程在堆上分配
比如有一个blueToothWorker,继承了QThread,实现了run方法。
用的时候如果直接
blueToothWorker bw;
那么该线程变量就是在函数栈上分配,一旦函数结束,线程没执行完,线程变量就被回收了。
正确用法是堆上分配。并通过connect函数自动回收。
void BluetoothMonitor::getBluetoothDataFromConDev()
{
blueToothWorker* bw = new blueToothWorker();
QObject::connect(bw, &QThread::finished, bw, &QObject::deleteLater);
bw->start();
}
成员指针变量的注意点
下面这种指针变量没有初始话,导致可能指向任意地址,也就是野指针。
记得初始化的时候指向nullptr
private:
QFileSystemWatcher *watcher;//文件监视器
QString filePath;//文件路径
blueToothWorker* bluetoothWorker; // 线程对象
如果delete以后,指针原本指向的地址被回收,但指针还是指向那里,造成悬空指针。
int *p = new int(10);
delete p; // p 成为悬空指针
*p = 20; // 未定义行为
记得delete以后指向nullptr
让线程安全停止
QThread没有给stop方法来停止线程。
QT所推荐的停止线程的方式,是自定义一个成员方法stop,在stop里面设置标志位,去控制run方法里面的循环。
void blueToothWorker::stop(){
running = false;
}
通过
QObject::connect(bluetoothWorker, &QThread::finished,
bluetoothWorker, &QObject::deleteLater);
来在线程任务完成后安全地回收线程。
同时,如果需要,可以调用wait方法来等待线程停止。
volatile的作用
前面提到了run里面跑while循环,现在,循环通过标志位volatile bool running来判断。
防止编译器优化
假设有一个变量flag
,用于表示某个操作是否完成。
在没有volatile
修饰的情况下,编译器可能会对代码进行优化,导致flag
的值被缓存在寄存器中,而不是每次都从内存中读取。
int flag = 0;
void checkFlag() {
while (flag == 0) {
// 等待flag被设置为1
}
// 执行后续操作
}
如果flag
被其他线程或中断服务程序修改为1,但编译器优化后将flag
的值缓存在寄存器中,那么checkFlag
函数中的while
循环可能会一直执行,因为寄存器中的值始终是0。
但如果将flag
声明为volatile
:
volatile int flag = 0;
编译器就不会对其进行优化,每次访问flag
时都会从内存中读取其值,从而能够及时检测到flag
的变化。
内存可见性
在一个多线程程序中,线程A修改一个变量count
,线程B需要读取count
的最新值。
int count = 0;
// 线程A
void incrementCount() {
count++;
}
// 线程B
void printCount() {
std::cout << count << std::endl;
}
如果没有volatile
修饰,线程A对count
的修改可能不会及时写回内存,导致线程B读取到的count
值不是最新的。
但如果将count
声明为volatile
,线程A对count
的修改会及时写回内存,线程B能够读取到最新的值。
禁止指令重排序
假设有一个变量data
和一个标志变量ready
,线程A先将data
赋值,然后将ready
设置为true
,线程B在ready
为true
时读取data
。
int data = 0;
bool ready = false;
// 线程A
void setData() {
data = 42;
ready = true;
}
// 线程B
void readData() {
if (ready) {
std::cout << data << std::endl;
}
}
如果没有volatile
修饰,编译器可能会对指令进行重排序,导致ready
先被设置为true
,而data
的赋值操作后置,这会导致线程B在ready
为true
时读取到data
的旧值。
volatile int data = 0;
volatile bool ready = false;
编译器不会对访问这些变量的指令进行重排序,从而保证了程序的逻辑正确性。
volatile常用场景
中断服务程序中修改的变量
多任务环境下的共享变量
存储器映射的硬件寄存器
volatile bool interruptFlag = false;
// 中断服务程序
void interruptHandler() {
interruptFlag = true;
}
// 主程序
void mainProgram() {
while (!interruptFlag) {
// 等待中断发生
}
// 处理中断
}
volatile bool taskCompleted = false;
// 任务A
void taskA() {
// 执行任务A的工作
taskCompleted = true;
}
// 任务B
void taskB() {
while (!taskCompleted) {
// 等待任务A完成
}
// 执行任务B的工作
}
volatile int* reg = reinterpret_cast<volatile int*>(0x1000); // 假设寄存器地址为0x1000
void readReg() {
int value = *reg; // 读取寄存器的值
std::cout << value << std::endl;
}
void writeReg(int value) {
*reg = value; // 写入寄存器
}