一、概述
调试ffmpeg的stop方法时,需要等待decode解码线程执行完成:
// 更新队列的工作状态,并唤醒阻塞中(wait)的线程
packets.setWork(0);
frames.setWork(0);
LOGE("AudioChannel::stop() pthread_join(pid_audio_decode,0)");
// 等待pid_audio_decode的线程执行完毕
pthread_join(pid_audio_decode,0);
......
然而调试时发现,卡在join这句不往下走了。
二、分析
预估是pid_audio_decode线程阻塞了,于是分析其代码片段:
void AudioChannel::decode() {
AVPacket *packet = 0;
LOGE("AudioChannel::decode begin");
while (isPlaying){
LOGE("AudioChannel::decode loop isPlaying = %d", isPlaying);
// 取出一个数据包
LOGE("AudioChannel::decode packets.pop(packet)");
int ret = packets.pop(packet);
if (!isPlaying){
break;
}
if (!ret){
continue;
}
......
}
}
通过log分析,while循环卡在这句 packets.pop(packet)了,再深入队列的pop()方法:
// 传递引用参数是为了修改它的值;用入参作为返回值可以返回多个(T& t1, T& t2...)
int pop(T& value){
int ret = 0;
pthread_mutex_lock(&mutex);
// 在多核处理器下 由于竞争可能虚假唤醒 包括jdk也说明了
while (q.empty()){
// 没有数据就等待
LOGE("pop : pthread_cond_wait");
pthread_cond_wait(&cond, &mutex);
}
if (!q.empty()){
value = q.front();
q.pop();
ret = 1;
}
LOGE("pop : pthread_mutex_unlock");
pthread_mutex_unlock(&mutex);
return ret;
}
有没有发现什么问题?如果队列q一直为空,即使通过条件变量通知唤醒wait操作,还是会一直循环下去,造成死循环,也就会阻塞其所在的线程,这就是pid_audio_decode线程阻塞的原因了。
三、解决方案
由于队列里面已经定义了一个是否工作的标记,可以用它来控制队列的同步操作
// 是否工作的标记 1 :工作 0:不接受数据 不工作
int work;
pop()方法优化如下:
int pop(T& value){
int ret = 0;
pthread_mutex_lock(&mutex);
// 在多核处理器下 由于竞争可能虚假唤醒 包括jdk也说明了
// 添加work判断,如果不工作了直接跳出循环,否则会一直wait造成死循环,从而阻塞线程
while (work && q.empty()){
// 没有数据就等待
LOGE("pop : pthread_cond_wait");
pthread_cond_wait(&cond, &mutex);
}
if (!q.empty()){
value = q.front();
q.pop();
ret = 1;
}
LOGE("pop : pthread_mutex_unlock");
pthread_mutex_unlock(&mutex);
return ret;
}
这样优化之后,如果想要停止队列的操作,可以预先设定work标记为0,则可以避免进入死循环了。