A线程平均每0.4ms发起一次lock(), 有时候要处理0.4ms以上才能unlock(), 此时A线程刚unlock()就会进入下一次lock(), B线程要跟A线程抢锁,情况差的时候要等几十ms才能拿到。
目前找到两种解决方法
- A线程每次unlock()之后,sleep(0), 相当于主动给其他线程一个机会
暂时放弃cpu,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。
当线程调用sleep(n)的时候,线程是由运行态转入等待态,线程被放入等待队列中,等待定时器n秒后的中断事件,线程才重新由等待态转入就绪态,被放入就绪队列中,等待队列中的线程是不参与cpu竞争的,只有就绪队列中的线程才会参与cpu竞争。
而sleep(0)之所以马上回去参与cpu竞争,是因为调用sleep(0)后,线程直接回到就绪队列。
每个Mutex都有一个等待队列,一个线程在mutex上挂起等待时, 首先把自己加入等待队列中,然后致线程状态为睡眠,然后调用OS的调度函数切换到别的线程。
unlock后,mutex等待队列中的线程被唤起,它的状态从等待态(=睡眠、挂起?)改为就绪态,被放入就绪队列。
如果不sleep(0),A线程unlock()时B线程被加到就绪队列,但是A继续运行,又拿到锁,没有给B线程lock()的机会,调度算法让B运行时,B lock(), 再次回到等待队列;
通过sleep(0), A线程放到就绪队列的队尾,B在A的前面,所以B先运行,可以拿到锁。
- 使用两个mutex
- data mutex (‘M’)
- next-to-access mutex (‘N’)
void FIFOlock(){
N.lock();
M.lock();
N.unlock();
}
void FIFOunlock(){
M.unlock();
}
N的作用是确保任一时刻最多只有一个线程能挂在M上,先执行FIFOlock()的线程拿到N,后执行FIFOlock()的挂在N上,因此FIFOunlock()时,一定只有第一个来的线程可以拿到M,然后释放N,让所有挂在N上的随机挑一个拿到N,开始挂在M上。这样FIFOlock()具有半FIFO的性质,只保证第一个进的第一个出,后面的不保证。
拓展:使用三个mutex实现优先锁,参见https://stackoverflow.com/a/11673600
- data mutex (‘M’)
- next-to-access mutex (‘N’)
- low-priority access mutex (‘L’)
这个半FIFO队列里,算上正在运行的线程,至多只能有一个低优先级线程。
void lowpriolock(){
L.lock();
FIFOlock();
}
void lowpriounlock(){
FIFOunlock();
L.unlock();
}
void highpriolock(){
FIFOlock();
}
void highpriounlock(){
FIFOunlock();
}
但是不太对,低优先级线程在半FIFO队列里还是有可能排在高优先级的前面。
是否有可能只靠mutex的lock,unlock, 顶多加上try_lock, 实现“有高优先级在等时,一定不会让低优先级的先上”?
一个用了额外int变量的方案:
atomic_int hpc(0);
mutex H,L;
void lowpriolock(){
L.lock();
}
void highpriolock(){
hpc++;
H.lock();
hpc--;
}
void unlock(){
if(hpc){
H.unlock();
}else{
L.unlock();
}
}