之前做一个实时操作系统时,系统是优先级抢占模式,树莓派4B是多核多线程开发,多个线程设置相同的优先级并安排在不同核上运行,可以实现线程并行;倘若优先级不一致,则优先级高的抢占,优先级低的阻塞,等待高优先级的运行完。
而QT是时间片轮转,在开发时没有注意,当多线程并行了,但其实不是,于是出现了这样的问题:
void RecvSignalThread::run()
{
while(ThreadCanRun) {
signal_block_mutex.lock();
while (needToblock) {
isblocking = true;
qDebug() << "signal_block_wait wait" ;
signal_block_wait.wait(&signal_block_mutex);
qDebug() << "signal_block_wait wake" ;
break;
}
isblocking = false;
.............
.............
signal_mutex.lock();
.............
.............
signal_mutex.unlock();
// qDebug() << "run mutex.unlock";
signal_block_mutex.unlock();
}
}
void ReplotThread::run()
{
while(ReplotThreadCanRun) {
plot_block_mutex.lock();
while(needToblock) {
isblocking = true;
qDebug() << "plot_block_wait wait" ;
plot_block_wait.wait(&plot_block_mutex);
qDebug() << "plot_block_wait wake" ;
break;
}
isblocking = false;
signal_mutex.lock();
.............
.............
signal_mutex.unlock();
qDebug() << "reploy mutex.unlock";
plot_block_mutex.unlock();
QThread::msleep(10);
}
}
问题:两个线程之间用互斥量通信,但是一个线程疯狂给互斥量上锁,但是另一个线程得过个一会才能抢到上锁机会的。而且我给那个疯狂上锁的线程加几个qdebug就不会了。
线程1保持实时性一直写入数据,线程2以100Hz的频率即10ms更新一次数据(读数据并显示),但是事实上并没有100Hz,有时候过了1s才刷新,间隔的时间不是确定的。难道互斥量被线程1上锁后,线程2不应该阻塞,然后等待线程1释放后,线程2立马再上锁吗,但是看起来线程2没有在互斥量被释放后立即抢到上锁的机会。
即使我把线程2的挂起10ms去掉了,抢到锁的机会也只是提高了一点,但是远没有100Hz。
另外如果我保持线程2的10ms挂起,并且给线程1加几个qdebug,线程2就可以达到100Hz.。
解决办法:
1. 再次学习线程、互斥量相关知识。
QT线程是时间片轮转。线程2在等信号量的时候,线程1大概率会锁着信号量,因为线程1释放掉信号量之后,又马上上锁了,这是原子性的,很快。所以即使时间片轮到线程2时,也很难出现线程1刚好释放信号量的情况,必须在线程1释放信号量和加锁信号量之间这极短的时间内,刚好时间片转完,轮到线程2才行。
互斥量的作用是防止出现数据不一致,或许这用不着互斥量。
以线程+挂起的方式实现一定频率的读取资源并不合适。轮到线程2的时间片时,线程2大概率在加锁的地方一直阻塞,这样时间片就阻塞很长时间,空消耗资源,减少了线程1的运行时间。
换成主线程中的定时器,不用线程,设置线程优先级,给线程1多点时间
换成定时器+movetothread,首先,qthread不是线程自身,是线程控制器。QThread是用来控制一个线程的,而不是线程自身。当run()函数结束之后,这个线程就会被终结,但创建的QThread对象仍然存在,且创建的子线程可以循环使用。start()函数可多次调用:QThread只是new出来的一个对象,当调用start()之后,它会新建一个线程,并把run()中的代码放到线程中运行,当运行完成后,线程会结束,但QThread对象还在,所以可再次调用start()函数。
moveToThread() 是 Qt 中用于将对象移动到另一个线程的方法。通过调用 moveToThread() 函数,可以将一个 QObject 对象从当前线程移动到另一个线程中,从而实现对象在新线程中执行特定的任务。定时器+movetothread的形式或许能够极大的减少占据主线程和线程1的时间,因为其中的对象运行结束应该就不再参与系统调度,直到下一次的触发信号,再跳出来参与系统调度。而线程+挂起的方式会长时间阻塞在加锁的位置,占据很大一块时间没有动作。
尝试再加一个互斥量和一个needtoreplotnew标志位,标志位为volatile BOOL,线程1只读,刷新的线程负责写,这样应该可以在保护大体量数据的同时,也不用对needtoreplotnew标志位加保护,我想。