Chapter1 Qt线程同步的几种方法

http://www.javashuo.com/article/p-gnyofzbl-gm.html

https://blog.csdn.net/hulifangjiayou/article/details/47018895

QT通过三种形式提供了对线程的支持。它们分别是,一、平台无关的线程类,二、线程安全的事件投递,三、跨线程的信号-槽连接。 这使得开发轻巧的多线程Qt程序更为容易,并能充分利用多处理器机器的优势。多线程编程也是一个有用的模式,它用于解决执行较长时间的操作而不至于用户界面失去响应。在Qt的早期版本中,在构建库时有不选择线程支持的选项,从4.0开始,线程总是有效的。

线程类

Qt 包含下面一些线程相关的类:
QThread 提供了开始一个新线程的方法
QThreadStorage 提供逐线程数据存储
QMutex 提供互斥锁,或互斥量
QMutexLocker 是一个便利类,它可以自动对QMutex加锁与解锁
QReadWriterLock 提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker 是便利类,它自动对QReadWriteLock加锁与解锁
QSemaphore 提供了一个整型信号量,是互斥量的泛化
QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。

原文链接:https://blog.csdn.net/mao834099514/article/details/51084572

一、QMutex类

QMutex类就像一把锁,在互斥量之前上锁(QMutex::lock()),然后在使用完互斥量之后解锁(QMutex::unlock())。比如下面的代码:

void someMethod()
{
    mutex.lock();
    qDebug()<<"Hello";
    qDebug()<<"World";
    mutex.unlock();
}
 
class Thread1 : public QThread  
{
protected:
    void run()
    {
        someMethod();
    }
};
 
class Thread2 : public QThread  
{
protected:
    void run()
    {
        someMethod();
    }
};

如上面的代码,在函数someMethod里面有两条语句,如果有两个线程启动之后,这两个线程都将调用这个函数(run函数即为线程启动后执行的程序),则可能会出现的结果是Hello Hello World World。但是这并不是我们想要的,我们希望的是每个线程可以一次性执行完someMethod函数里面的代码。这个时候我们便可以在函数俩面给函数体加上锁,然后在结束的时候解锁。
这里需要注意的是,如果一个线程试图向一个已经被其它线程上锁了互斥量上锁的话,这个线程将被阻塞,直到这个互斥量被解锁。如果一个线程希望自己在试图对一个上锁了的互斥量进行访问的时候能够不被阻塞,可以将lock()函数替换为tryLock()函数,这个函数的效果是:如果线程正在试图访问的互斥量已经被上锁了,那么可以立即返回而不被阻塞。

二、QMutexLocker便利类

使用QMutex对互斥量进行加锁解锁比较繁琐,在一些复杂的函数或者抛出C++异常的函数中都非常容易发生错误。可以使用一个方便的QMutexLocker类来简化对互斥量的处理。首先,QMutexLocker类的构造函数接收一个QMutex对象作为参数并且上锁,然后在析构函数中自动对其进行解锁。如下代码:

QMutex mutex;
 
void someMethod()
{
    QMutexLocker locker(&mutex);
    qDebug()<<"Hello";
    qDebug()<<"World";
}

这里创建一个QMutexLocker类实例,在这个实例的构造函数中将对mutex对象进行加锁。然后在析构函数中自动对mutex进行解锁。解锁的工作不需要显示地调用unlock函数,而是根据QMutexLocker对象的作用域绑定在一起了。

在复杂函数或者异常处理中,对mutex进行lock()和unlock()操作将会很复杂,进入点要lock(),在所有跳出点都要unlock(),想想都蛋疼!忘记unlock()将是很苦逼的事情,所以Qt引进了QMutexLocker来避免lock()和unlock()操作。

QMutex mutex;

QMutexLocker locker(&mutex);//在函数需要的地方建立QMutexLocker对象,并把mutex指针传进来就好了,后面什么事情也不用做了,等到退出函数后,这个QMutexLocker对象局部变量会自己销毁,销毁了就解锁了。方便吧。当然,你也可以调用locker.unlock()给mutex解锁,然后再调用locker.relock()再锁住mutex。也可以调用locker.mutex()获取建立locker时引入的那个mutex的指针。是不是很方便?

三、QReadWriteLock类

前两种保护互斥量的方法比较绝对,其达到的效果是:不管我要对互斥量做些是什么,我都要一个人霸占着,即使我只是看看它,也不能让别人看。这会使得这个互斥量资源的使用率大大下降,造成资源等待等问题。于是,我们可以对线程对互斥量的操作进行分类:读和写。有几种情况:
1、如果我只是看看的话,你也可以看,大家看到的都是正确的;
2、如果我要看这个数据,你是不能改的,不然我看到的就不知道是什么了;
3、我在改的时候,你不能看的,我可能会让你看到不正确的了;
4、我在改的时候,你当然不能改了。
因此,我们可以对QMutex锁进行升级,将其升级为QReadWriteLock,QMutex加锁的方法是lock(),而QReadWriteLock锁有两种锁法:设置为读锁(lockForRead())和写锁(lockForWrite())。代码如下:

QReadWriteLock lock;
void someMethod()
{
lock.lockForRead();
//lock.lockForWrite();
qDebug()<<"Hello";
qDebug()<<"World";

lock.unlock();
}

于是可能有一下三种情况:
1、一个线程试图对一个加了读锁的互斥量进行上读锁,允许;
2、一个线程试图对一个加了读锁的互斥量进行上写锁,阻塞;
3、一个线程试图对一个加了写锁的互斥量进行上读锁,阻塞;一个线程试图对一个加了写锁的互斥量进行上写锁,阻塞。
所以可以看出,读写锁比较适用的情况是:需要多次对共享的数据进行读操作的阅读线程。

四、QReadLocker便利类和QWriteLocker便利类对QReadWriteLock进行加解锁

和QMutex与QMutexLocker类的关系类似,关于读写锁也有两个便利类,读锁和写锁,QReadLocker和QWriteLocker。它们的构造函数都是一个QReadWriteLock对象,不同的是,在QReadLocker的构造函数里面是对读写锁进行lockForRead()加锁操作,而在QWriteLocker的构造函数里面是对读写锁进行lockForWrite()加锁操作。然后解锁操作unlock()都是在析构函数中完成的。

//这个允许多个进程同时读,但是只有一个写。而且写读不能同时进行。

QReadWriteLock lock;

void write()
{
lock.lockForWrite();//为写而锁
..........
lock.unlock();//解锁
}

void read()
{
lock.lockForRead();//为读而锁
..............
lock.unlock();//解锁
}

五、信号量QSemaphore

前面的几种锁都是用来保护只有一个量的互斥量的。但是还有些互斥量(资源)的数量并不止一个,比如一个电脑安装了2个打印机,我已经申请了一个,但是我不能霸占这两个,你来访问的时候如果发现还有空闲的仍然可以申请到的。于是这个互斥量可以分为两部分,已使用和未使用。一个线程在申请的时候,会对未使用到的部分进行加锁操作,如果加锁失败则阻塞,如果加锁成功,即又有一个资源被使用了,于是则将已使用到的部分解锁一个。
以著名的生产者消费者问题为例,分析问题:生产者须要的是空闲位置存放产品,结果是可取的产品多了一个。因而,咱们能够定义两个信号量:QSemaphore freeSpace和QSemaphore usedSpace,前者是给生产者使用的,后者是给消费者使用的。

//它的成员函数是
QSemaphore ( int n = 0 )//建立对象时可以给它n个资源
~QSemaphore ()
void acquire ( int n = 1 )// 这个操作一次减少n个资源,如果现有资源不到n个就会阻塞
int available () const   //返回当前可用的QSemaphore资源个数
void release ( int n = 1 )//这个操作一次增加n个资源
bool tryAcquire ( int n = 1 )//类似于acquire,但是申请不到n个资源时不会阻塞会立即返回
bool tryAcquire ( int n, int timeout )

//下面举生产者-消费者例子说明

 const int DataSize = 100000;//要生产的数据个数
 const int BufferSize = 8192;//用于盛数据的缓存大小
 char buffer[BufferSize];

//要定义两个信号量,一个用于表示自由空间,一个用于表示已用空间
 QSemaphore freeBytes(BufferSize);//自由空间初始化大小当然等于缓存大小啦
 QSemaphore usedBytes;

class Producer : public QThread
 {
 public:
     void run();
 };

 void Producer::run()
 {
     qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
     for (int i = 0; i < DataSize; ++i) {
         freeBytes.acquire();//申请一个自由空间,没有就阻塞
         buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
         usedBytes.release();//已用空间增加一个,有货啦!
     }
 }


class Consumer : public QThread
 {
 public:
     void run();
 };

 void Consumer::run()
 {
     for (int i = 0; i < DataSize; ++i) {
         usedBytes.acquire();
         fprintf(stderr, "%c", buffer[i % BufferSize]);
         freeBytes.release();
     }
     fprintf(stderr, "\n");
 }

int main(int argc, char *argv[])
 {
     QCoreApplication app(argc, argv);
     Producer producer;
     Consumer consumer;
     producer.start();
     consumer.start();
     producer.wait();
     consumer.wait();
     return 0;
 }

六、条件触发QWaitCondition

QWaitCondition最大的好处,我以为,是能在一个线程中唤醒一个或多个其它线程,固然前提是,其它线程在等待某个QWaitCondition,不然不起做用,你唤醒也没用。QWaitCondition必须与QMutex或者QReadwriteLock一块儿用。

QWaitCondition 的正确使用方法

原文链接:https://blog.csdn.net/flyoxs/article/details/54617342

简单用法

QWaitCondition 用于多线程的同步,一个线程调用QWaitCondition::wait() 阻塞等待,直到另一个线程调用QWaitCondition::wake() 唤醒才继续往下执行。

为了描述方便,这里假设主线程调用Send()往通信口发送一个数据包,然后阻塞等待回包才继续往下执行。另一个线程(通信线程)不断从通信口中接收数据并解析成数据包,然后唤醒主线程。下面是按网上给的最简单的方法:

// 示例一

// 主线程
Send(&packet);
mutex.lock();
condition.wait(&mutex); 
if (m_receivedPacket)
{
    HandlePacket(m_receivedPacket); // 另一线程传来回包
}
mutex.unlock();

// 通信线程
m_receivedPacket = ParsePacket(buffer);  // 将接收的数据解析成包
condition.wakeAll();

通常情况下,上述代码能跑得很好。但在某些特殊情况下,可能会出现混乱,大大降低通信可靠性。

在主线程中,调用 Send(&packet) 发送后,假如通信线程立即收到回包,在主线程还来不及调用 wait() 的时候,已经先 wakeAll() 了,显然这次唤醒是无效的,但主线程继续调用 wait(),然后一直阻塞在那里,因为该回的包已经回了。经测试出现这种现象的概率还是挺大的,因为我们不敢保证主线程总会被优先调度。即使主线程已经调用了 wait(),也不能保证底层操作系统的 wait_block 系统调用先于 wake 系统调用,毕竟wait() 函数也是层层封装的。

严谨用法

QWaitCondition::wait() 在使用时必须传入一个上锁的 QMutex 对象。这是很有必要的。而上述示例一代码中,我们虽然用了 mutex,但只是为了形式上传入QMutex参数,让编译器能正常编译而已,事实上,没有其它任何线程再用到这个mutex。而 mutex 本来就是让多个线程能协调工作的,所以上述示例一主线程用的 mutex 是无效的。

根据 Qt 手册,wait() 函数必须传入一个已上锁的 mutex 对象,在 wait() 执行过程中,mutex一直保持上锁状态,直到调用操作系统的wait_block 在阻塞的一瞬间把 mutex 解锁(严格说来应该是原子操作,即系统能保证在真正执行阻塞等待指令时才解锁)。另一线程唤醒后,wait() 函数将在第一时间重新给 mutex 上锁(这种操作也是原子的),直到显示调用 mutex.unlock() 解锁。

在通信线程也用上 mutex 后,整个通信时序正常了,完全解决了示例一的问题。代码如下:

// 示例二

// 主线程
mutex.lock();
Send(&packet);
condition.wait(&mutex); 
if (m_receivedPacket)
{
    HandlePacket(m_receivedPacket); // 另一线程传来回包
}
mutex.unlock();


// 通信线程
m_receivedPacket = ParsePacket(buffer);  // 将接收的数据解析成包
mutex.lock();
condition.wakeAll();
mutex.unlock();

上述示例二中,主线程先把 mutex 锁占据,即从发送数据包开始,一直到 QWaitCondition::wait() 在操作系统层次真正执行阻塞等待指令,这一段主线程的时间段内,mutex 一直被上锁,即使通信线程很快就接收到数据包,也不会直接调用 wakeAll(),而是在调用 mutex.lock() 时阻塞住(因为主线程已经把mutex占据上锁了,再尝试上锁就会被阻塞),直到主线程 QWaitCondition::wait() 真正执行操作系统的阻塞等待指令并释放mutex,通信线程的 mutex.lock() 才即出阻塞,继续往下执行,调用 wakeAll(),此时一定能唤醒主线程成功。

由此可见,通过 mutex 把有严格时序要求的代码保护起来,同时把 wakeAll() 也用同一个 mutex 保护起来,这样能保证:一定先有 wait() ,再有 wakeAll(),不管什么情况,都能保证这种先后关系,而不至于摆乌龙。

推而广之

mutex 和 condition 联合使用是多线程中的一个常用的设计模式,不仅是 Qt,对于 C++ 的 std::condition_variable 和 std::mutex ,以及 java 的 synchronized / wait / notify 也都适用。

使用Qt编写模块化插件式应用程序

https://blog.csdn.net/flyoxs/article/details/5546591

Chapter2 QReadLocker、QWriteLocker、qmutexlocker,在工业过程数据采集和处理显示场合使用,区别和特点,如何使用使程序更健壮和可靠

QMutexLocker、QReadLocker和QWriteLocker的使用。这些类都是用于简化互斥锁的管理,确保锁的释放,从而避免死锁。但它们的应用场景有所不同。

QMutexLocker:用于管理QMutex,在构造时锁定互斥锁,析构时解锁。适用于普通的互斥锁,即每次只有一个线程可以访问共享资源。

QReadLocker和QWriteLocker:用于管理QReadWriteLock(读写锁)。读写锁允许多个线程同时读取共享资源,但写入时只能有一个线程,并且写入时不能有读取。读锁是共享的,写锁是独占的。

在工业过程数据采集和处理显示场合,通常有多个线程:

数据采集线程:负责从硬件采集数据,并将数据写入共享缓冲区(写操作)。

数据处理线程:从共享缓冲区读取数据并进行处理(读操作)。

数据显示线程:从共享缓冲区读取数据并显示(读操作)。

由于读操作可以同时进行,而写操作需要独占,因此使用读写锁可以提高并发性能。具体使用哪种锁取决于对共享资源的操作类型。

使用这些锁管理器(Locker)而不是手动lock/unlock的好处是,即使在异常发生时,锁也能被正确释放,从而避免死锁。

下面我们分别给出使用示例,并说明如何使程序更健壮和可靠。

假设我们有一个共享的数据结构,用于存储采集的数据。

示例1:使用QMutexLocker(适用于简单的互斥锁,如果读和写没有明确区分,或者资源访问冲突频繁,使用互斥锁可能更简单,但并发性能可能较低)

示例2:使用QReadWriteLock和对应的QReadLocker/QWriteLocker(适用于读多写少的场景,可以提高并发性能)

我们更推荐在数据采集和处理显示场合使用读写锁,因为通常读线程(处理和显示)多于写线程(采集)。

QMutexLocker、QReadLocker、QWriteLocker在工业数据采集中的使用指南

一、三者的核心区别

在这里插入图片描述

二、工业数据采集系统架构示例

系统组件设计

// 数据模型:工业过程数据
class IndustrialProcessData {
private:
    // 核心数据字段
    struct DataBuffer {
        QVector<double> sensorValues;      // 传感器数值
        QVector<QDateTime> timestamps;     // 时间戳
        QMap<QString, double> parameters;  // 工艺参数
        bool systemAlarm = false;          // 系统报警状态
        
        // 统计信息
        struct Statistics {
            double average = 0.0;
            double max = -1e9;
            double min = 1e9;
            double stdDev = 0.0;
        } stats;
    };
    
    // 双缓冲设计:一个用于采集,一个用于显示
    DataBuffer activeBuffer;    // 活跃缓冲区(采集写入)
    DataBuffer displayBuffer;   // 显示缓冲区(UI读取)
    
    // 锁定义
    QMutex bufferSwapMutex;     // 缓冲区切换锁(使用QMutex)
    QReadWriteLock dataLock;    // 数据访问锁(使用读写锁)
    
    // 采集统计
    qint64 totalSamples = 0;
    qint64 errorCount = 0;
    
public:
    // 构造函数
    IndustrialProcessData() {
        // 初始化缓冲区大小
        activeBuffer.sensorValues.reserve(10000);
        activeBuffer.timestamps.reserve(10000);
        displayBuffer = activeBuffer;
    }
    
    // 数据采集接口(写操作)
    bool acquireData(double sensorValue, const QDateTime& timestamp) {
        // 使用QMutexLocker保护缓冲区切换
        QMutexLocker swapLocker(&bufferSwapMutex);
        
        // 使用QWriteLocker保护数据写入
        {
            QWriteLocker dataLocker(&dataLock);
            
            // 检查数据有效性
            if (std::isnan(sensorValue) || std::isinf(sensorValue)) {
                errorCount++;
                qWarning() << "无效传感器数据:" << sensorValue;
                return false;
            }
            
            // 写入活跃缓冲区
            activeBuffer.sensorValues.append(sensorValue);
            activeBuffer.timestamps.append(timestamp);
            
            // 更新统计信息(需要独占访问)
            updateStatistics(activeBuffer, sensorValue);
            
            // 检查报警阈值
            if (sensorValue > ALARM_THRESHOLD) {
                activeBuffer.systemAlarm = true;
                emit alarmTriggered(sensorValue, timestamp);
            }
            
            totalSamples++;
            
            // 保持缓冲区大小
            if (activeBuffer.sensorValues.size() > MAX_BUFFER_SIZE) {
                activeBuffer.sensorValues.removeFirst();
                activeBuffer.timestamps.removeFirst();
            }
        } // dataLocker析构,自动释放写锁
        
        return true;
    }
    
    // 数据处理接口(读操作)
    ProcessedData processData(int windowSize = 100) {
        ProcessedData result;
        
        // 使用QReadLocker保护数据读取
        {
            QReadLocker dataLocker(&dataLock);
            
            if (displayBuffer.sensorValues.isEmpty()) {
                return result;
            }
            
            // 读取最近的数据窗口
            int startIdx = qMax(0, displayBuffer.sensorValues.size() - windowSize);
            QVector<double> windowData = displayBuffer.sensorValues.mid(startIdx);
            QVector<QDateTime> windowTime = displayBuffer.timestamps.mid(startIdx);
            
            // 执行处理算法(在锁外部进行耗时计算)
            dataLocker.unlock();  // 提前释放锁,允许其他线程访问
            
            // 计算移动平均、FFT等(不持有锁)
            result.average = calculateMovingAverage(windowData);
            result.peakValue = findPeakValue(windowData);
            result.trend = calculateTrend(windowData);
            result.timestamps = windowTime;
        }
        
        return result;
    }
    
    // UI数据显示接口(读操作)
    DisplayData getDisplayData(int maxPoints = 1000) {
        DisplayData displayData;
        
        // 使用QReadLocker(允许多个UI线程同时读取)
        {
            QReadLocker dataLocker(&dataLock);
            
            displayData.values = displayBuffer.sensorValues;
            displayData.timestamps = displayBuffer.timestamps;
            displayData.stats = displayBuffer.stats;
            displayData.alarmState = displayBuffer.systemAlarm;
            
            // 限制显示点数以提高性能
            if (displayData.values.size() > maxPoints) {
                displayData.values = downsample(displayData.values, maxPoints);
                displayData.timestamps = downsample(displayData.timestamps, maxPoints);
            }
        }
        
        return displayData;
    }
    
    // 缓冲区同步(需要互斥操作)
    void swapBuffers() {
        // 使用QMutexLocker保证缓冲区切换的原子性
        QMutexLocker locker(&bufferSwapMutex);
        
        // 短暂的写锁定,更新显示缓冲区
        {
            QWriteLocker dataLocker(&dataLock);
            displayBuffer = activeBuffer;
            activeBuffer.sensorValues.clear();
            activeBuffer.timestamps.clear();
            activeBuffer.systemAlarm = false;
        }
        
        emit bufferSwapped(displayBuffer.sensorValues.size());
    }
    
private:
    // 内部统计更新(需要写锁)
    void updateStatistics(DataBuffer& buffer, double newValue) {
        buffer.stats.average = (buffer.stats.average * buffer.sensorValues.size() + newValue) 
                              / (buffer.sensorValues.size() + 1);
        buffer.stats.max = qMax(buffer.stats.max, newValue);
        buffer.stats.min = qMin(buffer.stats.min, newValue);
        
        // 标准差计算(简化)
        if (buffer.sensorValues.size() > 1) {
            double sumSq = 0.0;
            for (double val : buffer.sensorValues) {
                sumSq += (val - buffer.stats.average) * (val - buffer.stats.average);
            }
            buffer.stats.stdDev = sqrt(sumSq / buffer.sensorValues.size());
        }
    }
    
    // 数据处理算法(不持有锁)
    double calculateMovingAverage(const QVector<double>& data) {
        if (data.isEmpty()) return 0.0;
        double sum = 0.0;
        for (double val : data) sum += val;
        return sum / data.size();
    }
    
    // ... 其他算法实现
};

三、线程安全设计模式

1. 生产者-消费者模式(数据采集)

class DataCollector : public QThread {
    Q_OBJECT
private:
    IndustrialProcessData& sharedData;
    QMutex collectorMutex;  // 采集器状态锁
    bool running = true;
    
protected:
    void run() override {
        while (running) {
            // 1. 采集硬件数据
            double sensorValue = readFromHardware();
            QDateTime timestamp = QDateTime::currentDateTime();
            
            // 2. 写入共享数据(使用写锁)
            if (!sharedData.acquireData(sensorValue, timestamp)) {
                QMutexLocker locker(&collectorMutex);
                if (++consecutiveErrors > MAX_ERRORS) {
                    emit hardwareFailure();
                    break;
                }
            }
            
            // 3. 按采样率休眠
            QThread::usleep(SAMPLE_INTERVAL_US);
        }
    }
    
public:
    void stop() {
        QMutexLocker locker(&collectorMutex);  // 保护状态变量
        running = false;
    }
    
signals:
    void hardwareFailure();
    void dataAcquired(double value);
};

2. 观察者模式(数据显示)

class DataDisplay : public QObject {
    Q_OBJECT
private:
    IndustrialProcessData& sharedData;
    QTimer updateTimer;
    
public slots:
    void updateDisplay() {
        // 使用读锁获取显示数据(允许其他读操作并发)
        DisplayData data = sharedData.getDisplayData();
        
        // 在UI线程中更新界面(通过信号槽)
        emit dataUpdated(data);
        
        // 实时统计信息
        static int frameCount = 0;
        if (++frameCount % 60 == 0) {  // 每60帧计算一次性能
            calculateDisplayPerformance();
        }
    }
    
signals:
    void dataUpdated(const DisplayData& data);
};

3. 定时器驱动的数据处理

class DataProcessor : public QObject {
    Q_OBJECT
private:
    IndustrialProcessData& sharedData;
    QTimer processTimer;
    QElapsedTimer performanceTimer;
    
public slots:
    void processDataSlot() {
        performanceTimer.start();
        
        // 1. 获取数据(读锁,允许多个处理器同时读取)
        ProcessedData processed = sharedData.processData(PROCESSING_WINDOW);
        
        // 2. 执行复杂算法(不持有锁)
        performAdvancedAnalysis(processed);
        
        // 3. 触发后续操作
        if (processed.alarmDetected) {
            emit alarmAnalysisComplete(processed);
        }
        
        // 性能监控
        qint64 elapsed = performanceTimer.nsecsElapsed();
        emit processingTimeMeasured(elapsed / 1e6);  // 转换为毫秒
    }
};

四、健壮性和可靠性设计要点

1. 锁的作用域最小化

// ✅ 好的做法:锁的作用域尽可能小
void goodPractice() {
    // 准备工作(无锁)
    QVector<double> tempBuffer;
    prepareProcessing(tempBuffer);
    
    {
        // 仅保护必要的共享数据访问
        QWriteLocker locker(&dataLock);
        sharedData.append(tempBuffer);
    }  // 锁立即释放
    
    // 后处理(无锁)
    postProcessing();
}

// ❌ 坏的做法:锁的作用域过大
void badPractice() {
    QWriteLocker locker(&dataLock);  // 过早加锁
    
    // 耗时操作持有锁,阻塞其他线程
    QVector<double> tempBuffer = readFromExternalSource();  // 可能耗时
    
    sharedData.append(tempBuffer);
    
    performComplexCalculation();  // 复杂计算持有锁
    
}  // 锁释放太晚

2. 死锁预防策略

class SafeDataManager {
private:
    QReadWriteLock dataLock;
    QMutex configLock;
    QMutex ioLock;
    
    // 全局锁顺序定义
    enum LockOrder { CONFIG, DATA, IO };
    
public:
    void updateConfiguration() {
        // 按固定顺序加锁
        QMutexLocker configLocker(&configLock);     // 1. 配置锁
        QWriteLocker dataLocker(&dataLock);         // 2. 数据锁
        QMutexLocker ioLocker(&ioLock);             // 3. IO锁
        
        // 操作共享资源...
    }
    
    bool trySafeUpdate(int timeoutMs = 100) {
        // 使用尝试加锁,避免永久阻塞
        if (!configLock.tryLock(timeoutMs)) {
            qWarning() << "获取配置锁超时";
            return false;
        }
        
        if (!dataLock.tryLockForWrite(timeoutMs)) {
            configLock.unlock();  // 释放已获得的锁
            qWarning() << "获取数据写锁超时";
            return false;
        }
        
        // 使用locker确保异常安全
        QMutexLocker configLocker(&configLock);
        QWriteLocker dataLocker(&dataLock);
        
        // 执行操作...
        return true;
    }
};

3. 异常安全保证

class ExceptionSafeCollector {
public:
    void collectDataWithExceptionSafety() {
        // 使用locker保证即使抛出异常也能释放锁
        QWriteLocker locker(&dataLock);
        
        try {
            // 可能抛出异常的操作
            double value = readUnreliableSensor();
            processData(value);
            
            // 更复杂的操作
            if (value > CRITICAL_THRESHOLD) {
                throw std::runtime_error("临界值超限");
            }
            
            sharedData.append(value);
        }
        catch (const std::exception& e) {
            // locker会在栈展开时自动释放锁
            qCritical() << "数据采集异常:" << e.what();
            handleException(e);
            // 不需要手动解锁!
        }
        // locker在这里自动释放,即使发生异常
    }
};

4. 性能监控和调试

class InstrumentedLock {
private:
    QReadWriteLock lock;
    QElapsedTimer timer;
    qint64 totalReadTime = 0;
    qint64 totalWriteTime = 0;
    qint64 readCount = 0;
    qint64 writeCount = 0;
    
public:
    class ScopedReadLock {
        InstrumentedLock& parent;
        QReadLocker locker;
        QElapsedTimer scopeTimer;
    public:
        ScopedReadLock(InstrumentedLock& p) : parent(p), locker(&p.lock) {
            scopeTimer.start();
        }
        ~ScopedReadLock() {
            parent.totalReadTime += scopeTimer.nsecsElapsed();
            parent.readCount++;
        }
    };
    
    void logStatistics() const {
        qDebug() << "读锁统计: 次数=" << readCount 
                 << "总时间=" << totalReadTime / 1e6 << "ms"
                 << "平均=" << (readCount ? totalReadTime / readCount / 1e6 : 0) << "ms";
        qDebug() << "写锁统计: 次数=" << writeCount 
                 << "总时间=" << totalWriteTime / 1e6 << "ms"
                 << "平均=" << (writeCount ? totalWriteTime / writeCount / 1e6 : 0) << "ms";
    }
};

五、最佳实践总结

1. 选择正确的锁类型

  • QMutexLocker: 当读写频率相近、数据量小、或需要简单互斥时

  • QReadLocker: 当多个线程需要并发读取,且读操作远多于写操作时

  • QWriteLocker: 当需要独占写入,且写操作需要严格同步时

2. 工业场景建议

// 工业数据采集系统的最佳实践组合
class IndustrialSystem {
private:
    // 1. 高频采集数据使用读写锁
    QReadWriteLock realTimeDataLock;
    
    // 2. 配置信息使用互斥锁(写少读少)
    QMutex configLock;
    
    // 3. 设备控制使用互斥锁(需要严格互斥)
    QMutex deviceControlLock;
    
    // 4. 报警系统使用读写锁(多线程读取报警状态)
    QReadWriteLock alarmLock;
    
public:
    // 数据采集:频繁写入,使用写锁
    void acquireSample(double value) {
        QWriteLocker locker(&realTimeDataLock);
        // 快速写入
    }
    
    // 数据显示:频繁读取,使用读锁
    QVector<double> getDisplayData() {
        QReadLocker locker(&realTimeDataLock);
        // 快速读取,允许多个显示线程并发
    }
    
    // 配置更新:不频繁,使用互斥锁
    void updateConfig(const Config& config) {
        QMutexLocker locker(&configLock);
        // 更新配置
    }
    
    // 设备控制:需要严格互斥
    void controlDevice(DeviceCommand cmd) {
        QMutexLocker locker(&deviceControlLock);
        // 发送控制指令
    }
};

3. 健壮性检查清单

✅ 始终使用RAII锁管理器(QMutexLocker/QReadLocker/QWriteLocker)

✅ 锁的作用域最小化,尽快释放

✅ 避免在持有锁时调用可能阻塞的函数(IO、网络、UI操作)

✅ 固定多个锁的获取顺序,预防死锁

✅ 考虑使用tryLock机制防止永久阻塞

✅ 在异常处理中依赖RAII自动释放锁

✅ 监控锁竞争情况,优化热点区域

✅ 考虑无锁数据结构替代方案(如环形缓冲区)

4. 性能优化建议

1. 双缓冲技术: 一个缓冲区用于采集,一个用于显示,减少锁竞争

2. 批量处理: 批量读写减少锁获取次数

3. 无锁队列: 考虑使用QAtomic或第三方无锁队列

4. 分层锁策略: 不同数据使用不同锁,减少竞争范围

5. 读写分离: 区分实时数据和历史数据,使用不同的同步策略

在工业过程数据采集系统中,正确使用这些锁机制可以显著提高系统的可靠性和性能。关键是根据具体场景选择合适的锁类型,并遵循RAII原则确保资源安全。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐